diff --git a/AnimList.txt b/AnimList.txt new file mode 100644 index 0000000..88eb533 --- /dev/null +++ b/AnimList.txt @@ -0,0 +1,331 @@ +Common Animations +CODE +BOTH_ACTIVATEBELT1 +BOTH_ATTACK1 +BOTH_ATTACK3 +BOTH_BENCHSIT1_2STAND +BOTH_BENCHSIT1_FIXBOOT +BOTH_BENCHSIT1_IDLE +BOTH_BENCHSTAND2 +BOTH_BENCHSTAND2TO1 +BOTH_CONSOLE1 +BOTH_CONSOLE1IDLE +BOTH_CONSOLE1LEFT +BOTH_CONSOLE1RIGHT +BOTH_CONSOLE2 +BOTH_CONSOLE3 +BOTH_CONSOLE3IDLE +BOTH_CONSOLE3LEFT +BOTH_CONSOLE3RIGHT +BOTH_CONSOLETOSTAND1/BOTH_CONSTOSTAND1 +BOTH_COUCHSIT1_2STAND1 +BOTH_COUCHSIT1_GESTURELEFT +BOTH_COUCHSIT1_GESTURERIGHT +BOTH_COUCHSIT1_IDLE +BOTH_COUCHSIT1_TALKGESTURE +BOTH_COVERUP1_START +BOTH_COVERUP1_LOOP +BOTH_COVERUP1_END +BOTH_CRAWLBACK1 +BOTH_CROUCH1 +BOTH_CROUCH1IDLE +BOTH_CROUCH1WALK +BOTH_CROWDLOOK1 +BOTH_CROWDLOOK2 +BOTH_DEATH1 +BOTH_DEAD1 +BOTH_DEATH2 +BOTH_DEAD2 +BOTH_DEATHBACKWARD1 +BOTH_DEADBACKWARD1 +BOTH_DEATHBACKWARD2 +BOTH_DEADBACKWARD2 +BOTH_DEATHFORWARD1 +BOTH_DEADFORWARD1 +BOTH_DEATHFORWARD2 +BOTH_DEADFORWARD2 +BOTH_DIVE1 +BOTH_DROPANGERWEAP2 +BOTH_FALLDEATH1 +BOTH_FALLDEATH1INAIR +BOTH_FALLDEATH1LAND +BOTH_FALLDEAD1LAND +BOTH_FLOAT1 +BOTH_FLOAT2 +BOTH_FLOATCONSOLE1 +BOTH_GESTURE1 +BOTH_GESTURE2 +BOTH_GESTURE3 +BOTH_GET_UP1 +BOTH_GRAB1 +BOTH_GRAB2 +BOTH_GRABBED1 +BOTH_GRABBED2 +BOTH_GROUNDSHAKE1 +BOTH_GROUNDSHAKE2 +BOTH_GUARD_IDLE1 +BOTH_GUARD_LKRT1 +BOTH_GUARD_LOOKAROUND1 +BOTH_GUILT1 +BOTH_HALT1 +BOTH_INAIR1 +BOTH_INJURED1 +BOTH_INJURED3 +BOTH_JUMP1 +BOTH_JUMPBACK1 +BOTH_LADDER_DWN1 +BOTH_LADDER_UP1 +BOTH_LAND1 +BOTH_LANDBACK1 +BOTH_LEAN1 +BOTH_LEAN1TODROPHELM +BOTH_LYINGDEATH1 +BOTH_LYINGDEAD1 +BOTH_OFFLADDER_BOT1 +BOTH_OFFLADDER_TOP1 +BOTH_ONLADDER_BOT1 +BOTH_ONLADDER_TOP1 +BOTH_PAIN1 +BOTH_PAIN2 +BOTH_PAIN2WRITHE1 +BOTH_PSYCHICSHOCK1 +BOTH_PUSHTOSTAND1 +BOTH_RUN1 +BOTH_RUN1STOP +BOTH_RUN2 +BOTH_RUNINJURED1 +BOTH_SHIELD1 +BOTH_SHIELD2 +BOTH_SIT1STAND +BOTH_SIT1TO2 +BOTH_SIT1TO3 +BOTH_SIT2TO1 +BOTH_SIT2TO3 +BOTH_SIT3TO1 +BOTH_SIT3TO2 +BOTH_SIT4TO5 +BOTH_SIT4TO6 +BOTH_SIT5TO4 +BOTH_SIT5TO6 +BOTH_SIT6TO4 +BOTH_SIT6TO5 +BOTH_SIT7 +BOTH_SIT7TOSTAND1 +BOTH_STAND1 +BOTH_STAND1_RANDOM1 +BOTH_STAND1_RANDOM2 +BOTH_STAND1_RANDOM3 +BOTH_STAND1_RANDOM4 +BOTH_STAND1_RANDOM5 +BOTH_STAND1_RANDOM6 +BOTH_STAND1_RANDOM7 +BOTH_STAND1_RANDOM8 +BOTH_STAND1_RANDOM12 +BOTH_STAND2 +BOTH_STAND2TO4 +BOTH_STAND2_RANDOM1 +BOTH_STAND2_RANDOM2 +BOTH_STAND2_RANDOM3 +BOTH_STAND3 +BOTH_STAND4 +BOTH_STAND4TO2 +BOTH_STAND5 +BOTH_STAND6 +BOTH_STAND8 +BOTH_STAND9 +BOTH_STANDTOCONSOLE1 +BOTH_STANDTOWALK1 +BOTH_SURPRISED1 +BOTH_SURPRISED2 +BOTH_SURPRISED4 +BOTH_SURPRISED5 +BOTH_TABLE_EAT1 +BOTH_TABLE_GETUP1 +BOTH_TABLE_IDLE1 +BOTH_TABLE_TALKGESTURE1 +BOTH_UNCROUCH1 +BOTH_WALK1 +BOTH_WALK2 +BOTH_WALK3 +BOTH_WALK4 +BOTH_WALK7 +BOTH_WALKPUSH1 +BOTH_WALKTORUN1 +BOTH_WRITHING1 +LEGS_BACKTORSO_COMBADGE1 +LEGS_KNEELDOWN1 +LEGS_KNEELUP1 +LEGS_LEAN_LEFT1 +LEGS_LEAN_RIGHT1 +LEGS_TURN1 +LEGS_TURN2 +LEGS_WALKBACK1 +TORSO_COMBADGE2 +TORSO_COMBADGE3 +TORSO_DROPHELMET1 +TORSO_DROPWEAP1 +TORSO_EQUIPMENT1 +TORSO_EQUIPMENT2 +TORSO_GRABLBACKL +TORSO_HAND1 +TORSO_HAND2 +TORSO_HANDGESTURE1 +TORSO_HANDGESTURE2 +TORSO_HANDGESTURE3 +TORSO_HANDGESTURE4 +TORSO_HANDGESTURE5 +TORSO_HANDGESTURE6 +TORSO_HANDGESTURE7 +TORSO_HANDGESTURE8 +TORSO_HANDGESTURE9 +TORSO_HANDGESTURE10 +TORSO_HANDGESTURE11 +TORSO_HANDGESTURE12 +TORSO_HANDGESTURE13 +TORSO_HEADNOD1 +TORSO_HEADSHAKE1 +TORSO_HYPOSPRAY1 +TORSO_MEDICORDER1 +TORSO_POKERIDLE1 +TORSO_POKERIDLE2 +TORSO_POKERIDLE3 +TORSO_RAISEHELMET1 +TORSO_RAISEWEAP1 +TORSO_REACHHELMET1 +TORSO_SHOUT1 +TORSO_SPEECHLESS1 +TORSO_SPEECHLESS2 +TORSO_TRICORDER1 +TORSO_WEAPONIDLE1 +TORSO_WEAPONREADY1 +TORSO_WEAPONREADY2 + + +Male-only Animations +CODE +BOTH_ACTIVATEMEDKIT1 +BOTH_ASSIMILATED1 +BOTH_BENCHSIT1TO2 +BOTH_BENCHSIT2_IDLE +BOTH_BENCHSIT2STAND +BOTH_BENCHSIT2TO1 +BOTH_CATCH1 +BOTH_CROUCH2IDLE +BOTH_CROUCH2TOSTAND1 +BOTH_CROWDLOOK3 +BOTH_CROWDLOOK4 +BOTH_FALSEJUMP1 +BOTH_HEROSTANCE1 +BOTH_HITWALL1 +BOTH_INAIR1 +BOTH_INJURED2 +BOTH_INJURED6 +BOTH_INJURED6ATTACKSTART +BOTH_INJURED6ATTACKSTOP +BOTH_INJURED6COMBADGE +BOTH_INJURED6POINT +BOTH_LADDER_IDLE +BOTH_LAUGH1 +BOTH_LAUGH2 +BOTH_SCARED1 +BOTH_SCARED2 +BOTH_STAND1_RANDOM11 +BOTH_STAND1_RANDOM13 +BOTH_STAND7 +BOTH_SURPRISED3 +BOTH_WRITHING2 +LEGS_KNEEL1 +TORSO_ATTACK2 +TORSO_COFFEE +TORSO_PADD1 +TORSO_STAND2TOWEAPONREADY2 +TORSO_WRIST1 + + +Female-only Animations +CODE +BOTH_ATTACK2 +BOTH_BENCHSTAND1TO2 +BOTH_CARRIED1 +BOTH_CARRIED2 +BOTH_CONSOLE4 +BOTH_CONSOLE5 +BOTH_COUCHSIT1_TO2 +BOTH_COWAR1 +BOTH_DEAD1_FLOP +BOTH_DEAD2_FLOP +BOTH_DEAD3_FLOP +BOTH_DEAD4_FLOP +BOTH_DEAD5_FLOP +BOTH_DEAD6_FLOP +BOTH_DEAD7_FLOP +BOTH_DEADBACKWARD1_FLOP +BOTH_DEADBACKWARD2_FLOP +BOTH_DEADFORWARD1_FLOP +BOTH_DEADFORWARD2_FLOP +BOTH_DEATH3 +BOTH_DEAD3 +BOTH_DEATH4 +BOTH_DEAD4 +BOTH_DEATH5 +BOTH_DEAD5 +BOTH_DEATH6 +BOTH_DEAD6 +BOTH_DEATH7 +BOTH_DEAD7 +BOTH_FALLDEAD1_FLOP +BOTH_GET_UP2 +BOTH_GRAB3 +BOTH_GRAB4 +BOTH_HELP1 +BOTH_KNEELHAND1 +BOTH_LIFTED1 +BOTH_LYINGDEAD1_FLOP +BOTH_PAIN3 +BOTH_POSSESSED1 +BOTH_PSYCHICSHOCK2 +BOTH_RESTRAINED1 +BOTH_RESTRAINED1POINT +BOTH_RUNAWAY1 +BOTH_SHOCK1 +BOTH_SLEEP1 +BOTH_SLEEP1_NOSE +BOTH_SLEEP1GETUP +BOTH_SLEEP2 +BOTH_SLEEP2_SHIFT +BOTH_SLEEP2GETUP +BOTH_SLEEP3 +BOTH_SLEEP3DEATH +BOTH_SLEEP3DEAD +BOTH_SLEEP3GETUP +BOTH_SNAPTO1 +BOTH_STAND1_RANDOM12 +BOTH_STAND1_RANDOM14 +BOTH_STAND1TO2 +BOTH_STAND1TO3 +BOTH_STAND2_RANDOM4 +BOTH_STAND2_RANDOM5 +BOTH_STAND2_RANDOM6 +BOTH_STAND2_RANDOM7 +BOTH_STAND2TO1 +BOTH_STAND3TO1 +BOTH_STAND4TO5 +BOTH_STAND4TO6 +BOTH_STAND5TO4 +BOTH_STAND6TO4 +BOTH_STANDUP1 +BOTH_STEP1 +BOTH_TALKGESTURE1 +BOTH_TALKGESTURE2 +BOTH_TALKGESTURE3 +BOTH_TALKGESTURE4 +BOTH_TALKGESTURE5 +BOTH_TALKGESTURE6 +LEGS_JUMPB +LEGS_LANDB +LEGS_RUNBACK2 +TORSO_COMBADGE4 +TORSO_EQUIPMENT3 +TORSO_WEAPONIDLE2 +TORSO_WEAPONIDLE3 +TORSO_WEAPONREADY3 \ No newline at end of file diff --git a/BuildQVM.dsp b/BuildQVM.dsp new file mode 100644 index 0000000..b9e354c --- /dev/null +++ b/BuildQVM.dsp @@ -0,0 +1,101 @@ +# Microsoft Developer Studio Project File - Name="BuildQVM" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) External Target" 0x0106 + +CFG=BuildQVM - 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 "BuildQVM.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 "BuildQVM.mak" CFG="BuildQVM - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "BuildQVM - Win32 Release" (based on "Win32 (x86) External Target") +!MESSAGE "BuildQVM - Win32 Debug" (based on "Win32 (x86) External Target") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" + +!IF "$(CFG)" == "BuildQVM - 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 Cmd_Line "NMAKE /f BuildQVM.mak" +# PROP BASE Rebuild_Opt "/a" +# PROP BASE Target_File "BuildQVM.exe" +# PROP BASE Bsc_Name "BuildQVM.bsc" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Cmd_Line "buildvms.bat" +# PROP Rebuild_Opt "" +# PROP Target_File "..\baseef\game.qvm" +# PROP Bsc_Name "" +# PROP Target_Dir "" + +!ELSEIF "$(CFG)" == "BuildQVM - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "BuildQVM___Win32_Debug" +# PROP BASE Intermediate_Dir "BuildQVM___Win32_Debug" +# PROP BASE Cmd_Line "NMAKE /f BuildQVM.mak" +# PROP BASE Rebuild_Opt "/a" +# PROP BASE Target_File "BuildQVM.exe" +# PROP BASE Bsc_Name "BuildQVM.bsc" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "BuildQVM___Win32_Debug" +# PROP Intermediate_Dir "BuildQVM___Win32_Debug" +# PROP Cmd_Line "buildvms.bat" +# PROP Rebuild_Opt "" +# PROP Target_File "..\baseef\game.qvm" +# PROP Bsc_Name "" +# PROP Target_Dir "" + +!ENDIF + +# Begin Target + +# Name "BuildQVM - Win32 Release" +# Name "BuildQVM - Win32 Debug" + +!IF "$(CFG)" == "BuildQVM - Win32 Release" + +!ELSEIF "$(CFG)" == "BuildQVM - Win32 Debug" + +!ENDIF + +# Begin Source File + +SOURCE=.\buildvms.bat + +!IF "$(CFG)" == "BuildQVM - Win32 Release" + +# PROP Exclude_From_Build 1 + +!ELSEIF "$(CFG)" == "BuildQVM - Win32 Debug" + +# PROP Intermediate_Dir "Debug" +# PROP Exclude_From_Build 1 + +!ENDIF + +# End Source File +# End Target +# End Project diff --git a/BuildQVM.vcproj b/BuildQVM.vcproj new file mode 100644 index 0000000..a64e424 --- /dev/null +++ b/BuildQVM.vcproj @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HowTo compile for rpgxEF.txt b/HowTo compile for rpgxEF.txt new file mode 100644 index 0000000..169410e --- /dev/null +++ b/HowTo compile for rpgxEF.txt @@ -0,0 +1,3 @@ +To compile for rpgxEF change the following things: +q_shared.h line 54: #define XTRA 0 to #define XTRA 1 +bg_public.h line 6: #define XTRA 0 to #define XTRA 1 \ No newline at end of file diff --git a/Internal Version Change Log.txt b/Internal Version Change Log.txt new file mode 100644 index 0000000..4c196f3 --- /dev/null +++ b/Internal Version Change Log.txt @@ -0,0 +1,213 @@ +*********************************************************** +* VERSION CHANGE LOG (DO NOT REMOVE THIS HEADER * +* * +* Add a entry when you reciew the code, adding 1 to the * +* last number on the version, listing any new features * +* and fixes or modifications you have made to the code. * +* Use the format as seen in the other entries. * +* DO NOT make an entry every single time you compile! * +* Only as you add features and fixes. ***** +* * +* MAKE SURE THIS IS UP-TO-DATE BEFORE HANDING OVER THE CODE!! * +*************************************************************** + +v0.1.5 Modified By RedTechie Last Compile Date: 24/07/04 (Model System Dedicated Version) +========================================================================================= +Features Added: +-------------------------------------------- + + +Bugs Found: +-------------------------------------------- + + +Bugs Fixed: +-------------------------------------------- + + +Known Bugs: +--------------------------------------------- + + +Todo: +--------------------------------------------- + + + + + + +v0.1.4 Modified By J2J Last Compile Date: 24/07/04 (Model System Dedicated Version) +========================================================================================= +Features Added: +-------------------------------------------- + -Secondary Fire on TR-116 activates wireframe view to see thru walls in local area portal. (j2j) + -Changed model loading path to /models/players_rpgx/ (j2j) + -Updated the Animation.cfg parser function, imported string tables from single player, and removed extra info parsing (for now) (j2j) + -Single player model now load succesfully into holomatch game. (j2j) + + +Bugs Found: +-------------------------------------------- + + +Bugs Fixed: +-------------------------------------------- + -TR-116 nullshader tmp fixed by removing the guts of the wall hit function. (j2j) + + + + +Known Bugs: +--------------------------------------------- +Too many to list. + +Todo: +--------------------------------------------- +Fix animation that are played with various player movments +Add emote function +Rank System +etc. + + + + + +v0.1.3 Modified By RedTechie Last Compile Date: 22/07/04 +========================================================================================= +Features Added: +-------------------------------------------- + -Brand new scoreboard (Also fixed intermission scoreboards) + -New forcefield features(zap sound when touched, cvar controlable damage if 0 no health damage will occure, admins can remove forcefields but shooting at them with a alt grenade, force fields are now non damage taking, forcefields now last forever no time limit) + -added cvar rpg_invisibletripmines when set to 0 turns tripmines into time mines and every one can see them when set to 1 turned to tripmines and only admins can see them and walk threw them + -New health bar added first steps to health system ;) + -Flight and cloak status on left hand side of screen + -New n00b features (ghosted, nocliped, they change to the n00b class without a respawn, N00bs cant chat at all) + -Uncommeted out J2J's drag code probly going to get yelled out but its good code :D (only prob what would be cool is if we could get the play to hover in front of the player dragging him and always stay in front of him....kinda like the force in jk with the grip force) But this code is awsome for throwing N00bs off the map into the void :D + -Medics revive if cvar rpg_medicsrevive is turned on (1 or 0) + -Class selection in team menu now used the new rpg classes + -Cloaked admins have a sprite above there heads that only other admins can see saying there cloacked + +Bugs Found: +-------------------------------------------- + -nullshader for TR-116 :P (COUGH wallmark texture COUGH) + -Red line for TR-116 + +Bugs Fixed: +-------------------------------------------- + -md5 script removed on J2J's request + -Health bar now uses dat text files + +Known Bugs: +--------------------------------------------- + -Eng Tool's fire still dosnt remove mines when shooting at one trace won't trace a mind :S + -Shake cmd is not finished (its in g_cmds.c and cg_consolecmds.c) g_cmds.c calls it in cg_consolecmds.c it needs to loop to all clients on the server and that loop code needs to be in g_cmds.c so its not hackable + -Portal camera map entity still fricken sways its not a sea ship its a star ship + -Suicide while rpg_medicsrevive is on (forcekill, kill, forcekillradius) dosnt play death animation + +Todo: +--------------------------------------------- + -Effects gun + -Toggle switch for drag cmd + -MODEL SYSTEM! + -possibly NPC's :) + -apply the same invisible stuff to regular fire on grenades and also takes sounds out of both except for the explostion sound + -admin cmd to freeentity on all tripmines + -admin cmd to revive all + -MODEL SYSTEM! + -MODEL SYSTEM! + -MODEL SYSTEM! + -MODEL SYSTEM! + -MODEL SYSTEM! + -MODEL SYSTEM! + -MODEL SYSTEM! + -MODEL SYSTEM! + -MODEL SYSTEM! + + + + +v0.1.2 Modified By J2J Last Compile Date: 28/06/04 +========================================================================================= +Features Added: +-------------------------------------------- + Phaser and Phaser Compression Rifle sounds added from Nemisis. + Score Board: Score replace with Client Number (Hey its a rpg, who needs a score, right?) + +Bugs Found: +-------------------------------------------- + +Bugs Fixed: +-------------------------------------------- + Cloak and Flight now only availible for Admin class. + +Known Bugs: +--------------------------------------------- + + + +v0.1.1 Modified By J2J Last Compile Date: 23/06/04 +========================================================================================= +Features Added: +-------------------------------------------- + (Non-Code) - Swaped primary grenades to the single player version model.[In pak3.pk3, add all new contnent here till next release] -Now using extracted. + Added Easter Egg. + Added yet another mapper (yam) to the credits. + Transporter Tricorder now has personal coordinates + Transporter Power Up now has custom transport points. + Added chat commands to return server exe OS, and direct cmd to shutdown server (due to fuckface carter) + Added Chat to Area function, and made it default chat. + + +Bugs Found: +-------------------------------------------- + Ext. Laser bug: Flash light pile up effect still visible to other players when owner is in spec; + Also it piles up flash lights when in an invalid class, and no class is the right class! + Tripmines crash the server when a player comes into direct contact, assuming ent trace is going through, -causing a error + Cloaking only works if clients health is 40 or more. + Cloaking still availible for marines, possible oversight? + + +Bugs Fixed: +-------------------------------------------- + Trip mines now expload for non admin classes properly, and never for a passing admin; + Trip mines know don't crash the game ever, working perfectly; + Laser/Flashlight Problems fixed, so that admin and marine class can have laser, and no layering of lights. + Noclip spectating nolonger uses doors or transporter entities. + Changed all of Phenix's stricmp functions to Q_stricmp for QVM compile (:P) + + +Known Bugs: +--------------------------------------------- + No clip spectating still jerky. + + + +v0.1.0 Modified By Phenix Last Compile Date: 14/06/04 +========================================================================================= +Features Added: +------------------------------------------- + New UI color scheme change; + New UI Main Menu (RPG-X Credits, Player Model); + Version Info / Control; + Development Only: Rcon Get Command for use of selected RPG-X Team member only; + -This has been added due to the recent leak of an older preview version by Fuck-Face Carter + And everything before this. + + +Bugs Found: +------------------------------------------- + + +Bug Fixes: +------------------------------------------- + None + + +Known Bugs: +------------------------------------------- + Tripmines expload at random (mostly when the owner admin is NOT in admin class, then expload at anyone); + Noclip Spectating has jerky movment, still uses door transporter entities; + TR-116 produces 1000s of errors on primary fire, and secondary fire whilst invisible still has effect. + Bugs when using marine laser in admin class, it piles up flash lights. Same in the actual marine class. + Player Model on main menu does not change weapons with the weapon display as it should. \ No newline at end of file diff --git a/MakeAMod_readme.txt b/MakeAMod_readme.txt new file mode 100644 index 0000000..0fe892a --- /dev/null +++ b/MakeAMod_readme.txt @@ -0,0 +1,83 @@ +STEF Game Source. Copyright (C) 1999-2001 Raven Softare + +NOTE: The source MUST BE INSTALLED into the \stvoy\ directory. (q3asm will not work otherwise, and you cannot make the vms!) + +The Game Source is broken out into 3 areas. + +game - governs the game, runs on the server side. +cgame - governs the client side of the game, runs on the client side. +ui - handles the ui on the client side. + +Making a quick mod is very straightforward. This document assumes Microsoft Visual C++ v6.xx. It covers making a slight mod to the game source, recompiling for debugging and rebuilding the vm's for distribution. + +Slow Stasis Projectiles (Rockets) - TestMod +---------------------- +1. Open up the StefGame.dsw in Microsoft Visual C++. +2. Set the "game" project as the active project. +3. Open the "g_local.h" file and change the GAMEVERSION define from "baseef" to "TestMod" +4. Save "g_local.h" +5. Open the "g_missile.c" file. +6. Go to line 557 and change the 900 to 300. The old line reads: + + VectorScale( dir, 900, bolt->s.pos.trDelta ); + +The new line should read + + VectorScale( dir, 300, bolt->s.pos.trDelta ); + +7. Save "g_missile.c" +8. Perform a Build All command and it should build a DLL for the game. + +At this point you have two options. You can run the debugger, choosing 'stvoyHM.exe' as the executable host which will load your DLL for debugging or you can build the vm's for distribution. When you release mods, you will want to build new vm's. + +Building the vm's requires two things. + +1. The files contained in the bin_nt path must be available for execution ( lcc.exe and q3asm.exe ) +2. There must be environment variables set for proper lib and include paths. Microsoft Visual C++ installs a batch file that does this for you called "VCVARS32.bat" + +To build the sample vm for the slow rocket test, do the following: + +1. Open a DOS window. +2. Make sure lcc.exe and q3asm.exe are available on the path. +3. Run VCVARS32.bat +4. Go to your mods game directory and run the 'game.bat' file. + +This ultimately produces a 'qagame.qvm' in the \baseef\vm\ path. + +5. Make a "TestMod" path under your STEF directory. This will be a sibling to 'baseef' +6. Move 'qagame.qvm' to "\YourSTEFPath\TestMod\vm\" +7. Run STEF with the following command line "stvoyhm +set fs_game TestMod" +8. "TestMod" should be the referenced game and you should be able to catch up with and outrun your rockets. + + +Each of the areas contain a batch file "game.bat", "cgame.bat", and "ui.bat" which will build the appropriate vm files. + +----------------------------------------------- +Using Visual Studio to Build and Debug your Mod + +1. Create a directory to hold your Mod in the STEF directory + \YourSTEFPath\TestMod\ +2. In VC, open Project->Settings +3. On the Debug tab Category General: + -set the "Executable for debug session" to your stvoyHM.exe + -Set the "Working directory" to the same directory where stvoyHM.exe is + -Set the "Program arguments" to: +set r_fullscreen 0 +set viewlog 1 +set fs_game TestMod + (note, if you don't have the "set fs_game TestMod", you will need to create + a pak0.pk3 file for the mod to show up in the menu - see below) +4. On the link tab, change path of the "Output file name" to your TestMod directory + +Do this for each of projects you are making modifications to (cgame, game, ui) + +------------------------------------------------- +Making my Mod show up on the Mod list in the game + +You need to have a pak0.pk3 file in your mod directory +before it will show up on the in-game menu. Create a "description.txt" +file with some information about your game mod. Use WinZIP to create +the pak0.pk3 file. Add the "description.txt" file without compression +to the pak0.pk3 file and store it in your TestMod directory. You can +now put the .dll files in this directory, or create a "vm" directory +and place the .qvm files in the vm directory. + + + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b95b966 --- /dev/null +++ b/Makefile @@ -0,0 +1,50 @@ +default: qvm +qvm: build_qvm +so: build_so + +ZIP = zip + +build_qvm: + $(MAKE) -C Code-DM build_qvm +build_so: + $(MAKE) -C Code-DM build_so + +clean: + $(MAKE) -C Code-DM clean + rm -f baseef/*.so baseef/*.pk3 baseef/vm/*.qvm + +pak: qvm + cd baseef && rm -f pak4.pk3 && $(ZIP) -r pak4.pk3 vm/* +default: build + +build_qvm: + $(MAKE) -C game build_qvm + $(MAKE) -C cgame build_qvm + $(MAKE) -C ui build_qvm + +build_so: + $(MAKE) -C game build_so + $(MAKE) -C cgame build_so + $(MAKE) -C ui build_so + +clean: + $(MAKE) -C game clean + $(MAKE) -C cgame clean + $(MAKE) -C ui clean +default: qvm +qvm: build_qvm +so: build_so + +ZIP = zip + +build_qvm: + $(MAKE) -C Code-DM build_qvm +build_so: + $(MAKE) -C Code-DM build_so + +clean: + $(MAKE) -C Code-DM clean + rm -f baseef/*.so baseef/*.pk3 baseef/vm/*.qvm + +pak: qvm + cd baseef && rm -f pak4.pk3 && $(ZIP) -r pak4.pk3 vm/* diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..0d87516 --- /dev/null +++ b/README.txt @@ -0,0 +1,142 @@ +The Star Trek Voyager: Elite Force codebase project + +This project is meant to be a stable codebase with obvious bugs that +Ravensoft left in there removed and a few new features added. + +This project was originally started because my modification of the Quake3 +engine to run EF turned out to be a bit problematic. The SnapVector macro +yields slightly different results in the virtual machine interpreter of the +newer quake3 compared to the older quake3 eliteforce is based off. +As a result, you cannot jump as high as you could if you set com_maxfps to +some magic values like 74, 85, 125 etc.. +To get the same movement as with the original EliteForce in these new engine +releases the multiplayer VM code must be modified to emulate the old +behaviour. +So my goal is that every active player and every server will install these +modifications to allow for smooth movement on both, servers using the new +engine release and for good motion prediction on the client side. I realize +it may well be that this never happens as getting people to install new pak +files would be a major undertaking. I thought, if I attempt to do so in the +first place, I can as well try to get in a few improvements as long as they +don't change gameplay. I am not trying to insert new weapons, models, +gametypes etc... + +A few todos: + - Ignoring text messages from players that are a nuisance + - Unlagged code (can be switched on/off with a cvar) + +If you have made changes that do *not* change gameplay as mentioned above +and you think it really improves EliteForce, feel free to send the patches +to: arny@ats.s.bawue.de +If your code is reasonably clean, I will definitely add it to the +repository! + +Now for the using of this code: +I have left all non-C files from Ravensoft intact. This means, you should +probably be able to build this release under windows like you build the +original EF source released by raven. +For users on unixoid environments, I have included a few Makefiles that make +the job of building easier. The Makefiles are designed to work under Linux +so if you use another OS you may need to edit the three Makefiles included +in the game, cgame and ui directories to make this build properly. + +There are two different types of builds, namely the shared library and the +building of QVMs. +Building the shared objects only requires typing in "make so" +in the main directory where this README resides. If you have a working gcc +installation, this is all you need to do. The rest is being handled by the +Makefiles and in the end you should have three resulting files in the baseef +directory. +Building QVMs is easy, too. You need a few special bins, though, namely +q3lcc, q3rcc, q3cpp and q3asm. They can be found when building Quake3 from +icculus.org: http://icculus.org/quake3/ +Get the source code, compile it (you may want to only build the dedicated +server if you have no OpenGL support... consult the README in their project +on how this works). When compilation is done, the four required binaries can +be found in code/tools/. Copy the files to the bin/ directory that is in the +same dir with this README. +Now just do: "make qvm" in this dir and all QVMs should be in baseef/vm/. + +Careful when compiling both, QVMs and shared objects. gcc and the q3lcc +compiler produce incompatible output. +Make sure to run "make clean" before you change from QVM to shared objects +and vice versa. + +And last but not least: I already installed a mechanism to produce a pak +file that only works if you have zip installed. +make pak +will result in all three QVMs being packed into pak4.pk3 in baseef :) +Rename it as you see fit. + + - Thilo Schulz, 16.03.2006 +The Star Trek Voyager: Elite Force codebase project + +This project is meant to be a stable codebase with obvious bugs that +Ravensoft left in there removed and a few new features added. + +This project was originally started because my modification of the Quake3 +engine to run EF turned out to be a bit problematic. The SnapVector macro +yields slightly different results in the virtual machine interpreter of the +newer quake3 compared to the older quake3 eliteforce is based off. +As a result, you cannot jump as high as you could if you set com_maxfps to +some magic values like 74, 85, 125 etc.. +To get the same movement as with the original EliteForce in these new engine +releases the multiplayer VM code must be modified to emulate the old +behaviour. +So my goal is that every active player and every server will install these +modifications to allow for smooth movement on both, servers using the new +engine release and for good motion prediction on the client side. I realize +it may well be that this never happens as getting people to install new pak +files would be a major undertaking. I thought, if I attempt to do so in the +first place, I can as well try to get in a few improvements as long as they +don't change gameplay. I am not trying to insert new weapons, models, +gametypes etc... + +A few todos: + - Ignoring text messages from players that are a nuisance + - Unlagged code (can be switched on/off with a cvar) + +If you have made changes that do *not* change gameplay as mentioned above +and you think it really improves EliteForce, feel free to send the patches +to: arny@ats.s.bawue.de +If your code is reasonably clean, I will definitely add it to the +repository! + +Now for the using of this code: +I have left all non-C files from Ravensoft intact. This means, you should +probably be able to build this release under windows like you build the +original EF source released by raven. +For users on unixoid environments, I have included a few Makefiles that make +the job of building easier. The Makefiles are designed to work under Linux +so if you use another OS you may need to edit the three Makefiles included +in the game, cgame and ui directories to make this build properly. + +There are two different types of builds, namely the shared library and the +building of QVMs. +Building the shared objects only requires typing in "make so" +in the main directory where this README resides. If you have a working gcc +installation, this is all you need to do. The rest is being handled by the +Makefiles and in the end you should have three resulting files in the baseef +directory. +Building QVMs is easy, too. You need a few special bins, though, namely +q3lcc, q3rcc, q3cpp and q3asm. They can be found when building Quake3 from +icculus.org: http://icculus.org/quake3/ +Get the source code, compile it (you may want to only build the dedicated +server if you have no OpenGL support... consult the README in their project +on how this works). When compilation is done, the four required binaries can +be found in code/tools/. Copy the files to the bin/ directory that is in the +same dir with this README. +Now just do: "make qvm" in this dir and all QVMs should be in baseef/vm/. + +Careful when compiling both, QVMs and shared objects. gcc and the q3lcc +compiler produce incompatible output. +Make sure to run "make clean" before you change from QVM to shared objects +and vice versa. + +And last but not least: I already installed a mechanism to produce a pak +file that only works if you have zip installed. +make pak +will result in all three QVMs being packed into pak4.pk3 in baseef :) +Rename it as you see fit. + + - Thilo Schulz, 16.03.2006 diff --git a/RPG-X Lua Doc.txt b/RPG-X Lua Doc.txt new file mode 100644 index 0000000..b666b2f --- /dev/null +++ b/RPG-X Lua Doc.txt @@ -0,0 +1,39 @@ +library entity: + Avaible: + entity.FindNumber(int num) --> finds and returns an entity by it's entity index number + entity.Find(string targetname) --> finds and returns an entity by it's targetname + entity.FindBModel(string bmodel) --> Find an entity by its brush model + entity.Target(entity ent) --> returns one of the targets of an entity + entity.Teleport(entity ent, entity target) --> Teleports a player to an other entity + entity.IsRocket(entity ent) --> Checks if an entity is a rocket + entity.IsGrenade(entity ent) --> Checks if an entity is a grenade + entity.Spawn(void) --> Spawn a new entity if possible + entity.GetNumber(entity ent) --> Returns an entities index number + entity.IsClient(entity ent) --> Checks if an entity is a client + entity.GetClientName(entity ent) --> Returns the display name of a client + entity.Print --> + entity.CenterPrint --> + entity.GetClassname(entity ent) --> Returns the classname of an entity + entity.SetClassname(entity ent, string name) --> Sets the classname of an entity to namer + entity.GetTargetname(entity ent) --> Returns the targetname of an entity + entity.Rotate(entity ent, vector dir --> Rotates an entity in the specified directions + entity.__tostring(entity ent) --> Prints an entity as string + entity.CallSpawn(entity ent) --> Calls the spawn function for an entity + entity.Remove(entity ent) --> Removes an entity if it is not protected (eg. Players) + ToDo: + entity.SetField(entity ent, string field) --> Set a field of an entity to a new value + entity.SetValue(entity ent, string value) --> Set a member of gentity_s to new value (for members that are not in fields_t) + +library qmath: + Avaible: + qmath.fabs(float num) --> Returns the integer part of a floating point number + qmath.sin(float degree) --> Implementation of Sinus function, takes degree as argument not radian + qmath.cos(float degree) --> Implementation of Cosinus function, takes degree as argument not radian + qmath.tan(float degree) --> Implementation of Tan function, takes degree as argument not radian + qmath.asin(float number) --> ~ + qmath.acos(float number) --> ~ + qmath.atan(float number) --> ~ + + qmath.ceil(float number) --> rounds up + qmath.floor(float number) --> rounds down + \ No newline at end of file diff --git a/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation Deu.aux b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation Deu.aux new file mode 100644 index 0000000..8ca4989 --- /dev/null +++ b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation Deu.aux @@ -0,0 +1,279 @@ +\relax +\catcode`"\active +\select@language{ngerman} +\@writefile{toc}{\select@language{ngerman}} +\@writefile{lof}{\select@language{ngerman}} +\@writefile{lot}{\select@language{ngerman}} +\@writefile{toc}{\contentsline {chapter}{\numberline {1}Einf\IeC {\"u}hrung}{7}} +\@writefile{lof}{\addvspace {10\p@ }} +\@writefile{lot}{\addvspace {10\p@ }} +\newlabel{intro}{{1}{7}} +\@writefile{toc}{\contentsline {section}{\numberline {1.1}Grundlegende Informationen}{7}} +\newlabel{gen-info}{{1.1}{7}} +\@writefile{toc}{\contentsline {section}{\numberline {1.2}Vorvereinbarungen}{7}} +\newlabel{preq}{{1.2}{7}} +\@writefile{toc}{\contentsline {chapter}{\numberline {2}Lua Hooks}{9}} +\@writefile{lof}{\addvspace {10\p@ }} +\@writefile{lot}{\addvspace {10\p@ }} +\newlabel{lua-hooks}{{2}{9}} +\@writefile{toc}{\contentsline {section}{\numberline {2.1}Was ist ein Lua Hook}{9}} +\newlabel{wia-lh}{{2.1}{9}} +\@writefile{toc}{\contentsline {section}{\numberline {2.2}Statische Lua Hooks}{9}} +\newlabel{s-lh}{{2.2}{9}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.2.1}InitGame}{9}} +\newlabel{init-game}{{2.2.1}{9}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.2.2}ShutdownGame}{9}} +\newlabel{shutdown-game}{{2.2.2}{9}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.2.3}RunFrame}{10}} +\newlabel{run-frame}{{2.2.3}{10}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.2.4}GClientPrint}{10}} +\newlabel{cli-print}{{2.2.4}{10}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.2.5}GPrint}{10}} +\newlabel{g-print}{{2.2.5}{10}} +\@writefile{toc}{\contentsline {section}{\numberline {2.3}Dynamische Lua Hooks}{11}} +\newlabel{dyn-lh}{{2.3}{11}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3.1}luaThink}{11}} +\newlabel{luaThink}{{2.3.1}{11}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3.2}luaTouch}{11}} +\newlabel{luaTouch}{{2.3.2}{11}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3.3}luaUse}{11}} +\newlabel{luaUse}{{2.3.3}{11}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3.4}luaHurt}{11}} +\newlabel{luaHurt}{{2.3.4}{11}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3.5}luaDie}{11}} +\newlabel{luaDie}{{2.3.5}{11}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3.6}luaFree}{12}} +\newlabel{luaFree}{{2.3.6}{12}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3.7}luaReached}{12}} +\newlabel{luaReached}{{2.3.7}{12}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3.8}luaReachedAngular}{12}} +\newlabel{luaReachedAngular}{{2.3.8}{12}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3.9}luaTrigger}{12}} +\newlabel{luaTrigger}{{2.3.9}{12}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3.10}luaSpawn}{12}} +\newlabel{luaSpawn}{{2.3.10}{12}} +\@writefile{toc}{\contentsline {chapter}{\numberline {3}RPG-X2 Map Scripting}{13}} +\@writefile{lof}{\addvspace {10\p@ }} +\@writefile{lot}{\addvspace {10\p@ }} +\newlabel{rpgx2-mapscripting}{{3}{13}} +\@writefile{toc}{\contentsline {section}{\numberline {3.1}Map scripts}{13}} +\newlabel{map-scripts}{{3.1}{13}} +\@writefile{toc}{\contentsline {section}{\numberline {3.2}Aufruf von Funktionen}{13}} +\newlabel{map-callingfunction}{{3.2}{13}} +\@writefile{toc}{\contentsline {chapter}{\numberline {4}RPG-X2 Lua Bibliotheken}{15}} +\@writefile{lof}{\addvspace {10\p@ }} +\@writefile{lot}{\addvspace {10\p@ }} +\newlabel{rpgx2-llibs}{{4}{15}} +\@writefile{toc}{\contentsline {section}{\numberline {4.1}game}{15}} +\newlabel{g}{{4.1}{15}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.1.1}game.Print}{15}} +\newlabel{g-prnt}{{4.1.1}{15}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.1.2}game.ClientPrint}{15}} +\newlabel{g-clientprint}{{4.1.2}{15}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.1.3}game.CenterPrint}{15}} +\newlabel{g-centerprint}{{4.1.3}{15}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.1.4}game.MessagePrint}{15}} +\newlabel{g-messagepritn}{{4.1.4}{15}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.1.5}game.LevelTime}{16}} +\newlabel{g-leveltime}{{4.1.5}{16}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.1.6}game.SetGlobal}{16}} +\newlabel{g-setglobal}{{4.1.6}{16}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.1.7}game.GetGlobal}{16}} +\newlabel{g-getglobal}{{4.1.7}{16}} +\@writefile{toc}{\contentsline {section}{\numberline {4.2}qmath}{17}} +\newlabel{qmath}{{4.2}{17}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.1}qmath.abs}{17}} +\newlabel{qm-abs}{{4.2.1}{17}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.2}qmath.sin}{17}} +\newlabel{qm-sin}{{4.2.2}{17}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.3}qmath.cos}{17}} +\newlabel{qm-cos}{{4.2.3}{17}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.4}qmath.tan}{17}} +\newlabel{qm-tan}{{4.2.4}{17}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.5}qmath.asin}{17}} +\newlabel{qm-asin}{{4.2.5}{17}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.6}qmath.acos}{17}} +\newlabel{qm-acos}{{4.2.6}{17}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.7}qmath.atan}{17}} +\newlabel{qm-atan}{{4.2.7}{17}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.8}qmath.floor}{17}} +\newlabel{qm-floor}{{4.2.8}{17}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.9}qmath.ceil}{18}} +\newlabel{qm-ceil}{{4.2.9}{18}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.10}qmath.fmod}{18}} +\newlabel{qm-fmod}{{4.2.10}{18}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.11}qmath.modf}{18}} +\newlabel{qm-modf}{{4.2.11}{18}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.12}qmath.sqrt}{18}} +\newlabel{qm-sqrt}{{4.2.12}{18}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.13}qmath.log}{18}} +\newlabel{qm-log}{{4.2.13}{18}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.14}qmath.log10}{18}} +\newlabel{qm-log10}{{4.2.14}{18}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.15}qmath.deg}{18}} +\newlabel{qm-deg}{{4.2.15}{18}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.16}qmath.rad}{18}} +\newlabel{qm-rad}{{4.2.16}{18}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.17}qmath.frexp}{18}} +\newlabel{qm-frexp}{{4.2.17}{18}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.18}qmath.ldexp}{19}} +\newlabel{qm-ldexp}{{4.2.18}{19}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.19}qmath.min}{19}} +\newlabel{qm-min}{{4.2.19}{19}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.20}qmath.max}{19}} +\newlabel{qm-max}{{4.2.20}{19}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.21}qmath.random}{19}} +\newlabel{qm-random}{{4.2.21}{19}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.22}qmath.crandom}{19}} +\newlabel{qm-crandom}{{4.2.22}{19}} +\@writefile{toc}{\contentsline {section}{\numberline {4.3}vector}{20}} +\newlabel{vect}{{4.3}{20}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3.1}vector.New}{20}} +\newlabel{vect-new}{{4.3.1}{20}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3.2}vector.Construct}{20}} +\newlabel{vect-cons}{{4.3.2}{20}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3.3}vector.Set}{20}} +\newlabel{vect-set}{{4.3.3}{20}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3.4}vector.clear}{20}} +\newlabel{vect-clear}{{4.3.4}{20}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3.5}vector.Add}{20}} +\newlabel{vect-add}{{4.3.5}{20}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3.6}vector.Substract}{20}} +\newlabel{vect-sub}{{4.3.6}{20}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3.7}vector.Scale}{20}} +\newlabel{vect-scale}{{4.3.7}{20}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3.8}vector.Length}{21}} +\newlabel{vect-length}{{4.3.8}{21}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3.9}vector.Normalize}{21}} +\newlabel{vect-norm}{{4.3.9}{21}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3.10}vector.RotateAroundPoint}{21}} +\newlabel{vect-rotarndpnt}{{4.3.10}{21}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3.11}vector.Perpendicular}{21}} +\newlabel{vect-Perpendicular}{{4.3.11}{21}} +\@writefile{toc}{\contentsline {section}{\numberline {4.4}entity}{22}} +\newlabel{enty}{{4.4}{22}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.1}entity.Find}{22}} +\newlabel{enty-find}{{4.4.1}{22}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.2}entity.FindNumber}{22}} +\newlabel{enty.findnumber}{{4.4.2}{22}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.3}entity.FindBModel}{22}} +\newlabel{enty-findbmodel}{{4.4.3}{22}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.4}ent.GetNumber}{22}} +\newlabel{enty-getnumber}{{4.4.4}{22}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.5}ent.SetKeyValue}{22}} +\newlabel{enty-setkeyvalue}{{4.4.5}{22}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.6}entity.Remove}{22}} +\newlabel{enty-remove}{{4.4.6}{22}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.7}ent.GetOrigin}{23}} +\newlabel{enty-getorigin}{{4.4.7}{23}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.8}ent.IsClient}{23}} +\newlabel{enty-isclient}{{4.4.8}{23}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.9}ent.GetClientname}{23}} +\newlabel{enty-getclientname}{{4.4.9}{23}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.10}ent.GetClassname}{23}} +\newlabel{enty-getclassname}{{4.4.10}{23}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.11}ent.SetClassname}{23}} +\newlabel{enty-setclassname}{{4.4.11}{23}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.12}ent.GetTargetname}{23}} +\newlabel{enty-gettargetname}{{4.4.12}{23}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.13}ent.SetupTrigger}{23}} +\newlabel{enty-setuptrigger}{{4.4.13}{23}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.14}entity.GetTarget}{23}} +\newlabel{enty-gettarget}{{4.4.14}{23}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.15}entity.Use}{23}} +\newlabel{enty-use}{{4.4.15}{23}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.16}entity.Spawn}{24}} +\newlabel{enty-spawn}{{4.4.16}{24}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.17}entiy.CallSpawn}{24}} +\newlabel{enty-callspawn}{{4.4.17}{24}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.18}entity.DelayedCallSpawn}{24}} +\newlabel{enty-delayedcallspawn}{{4.4.18}{24}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.19}entity.RemoveSpawns}{24}} +\newlabel{enty-removespawns}{{4.4.19}{24}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.20}ent.Lock}{24}} +\newlabel{enty-lock}{{4.4.20}{24}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.21}ent.Unlock}{24}} +\newlabel{enty-unlock}{{4.4.21}{24}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.22}ent.IsLocked}{24}} +\newlabel{enty-locked}{{4.4.22}{24}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.23}ent.GetParm}{24}} +\newlabel{enty-getparm}{{4.4.23}{24}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.24}ent.SetParm}{25}} +\newlabel{enty-setparm}{{4.4.24}{25}} +\@writefile{toc}{\contentsline {section}{\numberline {4.5}mover}{26}} +\newlabel{mver}{{4.5}{26}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.5.1}mover.Halt}{26}} +\newlabel{mver-halt}{{4.5.1}{26}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.5.2}mover.HaltAngles}{26}} +\newlabel{mver-haltangles}{{4.5.2}{26}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.5.3}mover.AsTrain}{26}} +\newlabel{mver-astrain}{{4.5.3}{26}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.5.4}mover.SetAngles}{26}} +\newlabel{mver-setangles}{{4.5.4}{26}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.5.5}mover.SetPosition}{26}} +\newlabel{mver-setposition}{{4.5.5}{26}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.5.6}mover.ToAngles}{26}} +\newlabel{mver-toangles}{{4.5.6}{26}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.5.7}mover.ToPosition}{27}} +\newlabel{mver-toposition}{{4.5.7}{27}} +\@writefile{toc}{\contentsline {section}{\numberline {4.6}sound}{28}} +\newlabel{sound}{{4.6}{28}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.6.1}Sound Kan\IeC {\"a}le}{28}} +\newlabel{sound-chan}{{4.6.1}{28}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.6.2}sound.PlaySound}{28}} +\newlabel{snd-playsnd}{{4.6.2}{28}} +\@writefile{toc}{\contentsline {chapter}{\numberline {5}Beispiele}{29}} +\@writefile{lof}{\addvspace {10\p@ }} +\@writefile{lot}{\addvspace {10\p@ }} +\newlabel{examples}{{5}{29}} +\@writefile{toc}{\contentsline {section}{\numberline {5.1}Beispiel 1 - Hallo Welt}{29}} +\newlabel{example1}{{5.1}{29}} +\@writefile{toc}{\contentsline {subsection}{\numberline {5.1.1}Hallo Welt f\IeC {\"u}r game}{29}} +\newlabel{example11}{{5.1.1}{29}} +\newlabel{helloworldgame}{{5.1}{29}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {5.1}Hallo Welt f\IeC {\"u}r game}{29}} +\@writefile{toc}{\contentsline {subsection}{\numberline {5.1.2}Hallo Welt f\IeC {\"u}r einen Spieler}{29}} +\newlabel{example12}{{5.1.2}{29}} +\newlabel{helloworldclient}{{5.2}{29}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {5.2}Hallo Welt f\IeC {\"u}r Spieler}{29}} +\newlabel{helloworldclient1}{{5.3}{30}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {5.3}First function}{30}} +\newlabel{helloworldclient2}{{5.4}{30}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {5.4}Second function}{30}} +\newlabel{helloworldclient3}{{5.5}{30}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {5.5}Third function}{30}} +\@writefile{toc}{\contentsline {subsection}{\numberline {5.1.3}Hallo Welt f\IeC {\"u}r alle Spieler}{30}} +\newlabel{example13}{{5.1.3}{30}} +\newlabel{helloworldclientall}{{5.6}{30}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {5.6}Hallo Welt f\IeC {\"u}r alle Spieler}{30}} +\@writefile{toc}{\contentsline {section}{\numberline {5.2}Beispiel 2 - Entities Finden}{30}} +\newlabel{example2}{{5.2}{30}} +\@writefile{toc}{\contentsline {subsection}{\numberline {5.2.1}Entities \IeC {\"u}ber ihren targetname finden}{31}} +\newlabel{example21}{{5.2.1}{31}} +\newlabel{findents1}{{5.7}{31}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {5.7}Entity \IeC {\"u}ber ihren targername finden}{31}} +\@writefile{toc}{\contentsline {subsection}{\numberline {5.2.2}Entities \IeC {\"u}ber ihre Entitynummer finden}{31}} +\newlabel{example22}{{5.2.2}{31}} +\newlabel{findents2}{{5.8}{31}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {5.8}Entities \IeC {\"u}ber ihre Entitynummer finden}{31}} +\@writefile{toc}{\contentsline {subsection}{\numberline {5.2.3}Entities \IeC {\"u}ber ihr Brush Modell finden}{31}} +\newlabel{example23}{{5.2.3}{31}} +\newlabel{findents3}{{5.9}{31}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {5.9}Entities \IeC {\"u}ber ihr Brush Modell finden}{31}} +\@writefile{toc}{\contentsline {section}{\numberline {5.3}Beispiel 3 - Entities Spawnen}{31}} +\newlabel{spawnents}{{5.10}{32}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {5.10}Eine Entity Spawnen}{32}} +\@writefile{toc}{\contentsline {chapter}{\numberline {6}Wie man ...}{35}} +\@writefile{lof}{\addvspace {10\p@ }} +\@writefile{lot}{\addvspace {10\p@ }} +\newlabel{howto}{{6}{35}} +\@writefile{toc}{\contentsline {section}{\numberline {6.1}Turbolifte zu \IeC {\"a}lteren RPG-X Maps hinzuf\IeC {\"u}gt}{35}} +\newlabel{howto-x2turbo}{{6.1}{35}} +\@writefile{toc}{\contentsline {section}{\numberline {6.2}Transporter die das ui\_transporter benutzen zu \IeC {\"a}lteren Maps hinzuf\IeC {\"u}gt}{35}} +\newlabel{howto-uitrans}{{6.2}{35}} +\@writefile{toc}{\contentsline {section}{\numberline {6.3}func\_usable zu func\_forcefield konvertiert}{35}} +\newlabel{howto-usabletoforcefield}{{6.3}{35}} +\newlabel{fustoff}{{6.1}{36}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {6.1}Beispiel 1}{36}} +\newlabel{fustoff2}{{6.2}{36}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {6.2}Beispiel 2}{36}} diff --git a/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation Deu.log b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation Deu.log new file mode 100644 index 0000000..f86c694 --- /dev/null +++ b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation Deu.log @@ -0,0 +1,536 @@ +This is pdfTeX, Version 3.1415926-1.40.11 (MiKTeX 2.9) (preloaded format=pdflatex 2010.12.30) 30 DEC 2010 13:11 +entering extended mode +**C:/stvoy/Code-DM/RPG-X2*Lua*Documentation/RPG-X2*Lua*Documentation*Deu.tex +("C:/stvoy/Code-DM/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation Deu.tex" +LaTeX2e <2009/09/24> +Babel and hyphenation patterns for english, afrikaans, ancientgreek, ar +abic, armenian, assamese, basque, bengali, bokmal, bulgarian, catalan, coptic, +croatian, czech, danish, dutch, esperanto, estonian, farsi, finnish, french, ga +lician, german, german-x-2009-06-19, greek, gujarati, hindi, hungarian, iceland +ic, indonesian, interlingua, irish, italian, kannada, kurmanji, lao, latin, lat +vian, lithuanian, malayalam, marathi, mongolian, mongolianlmc, monogreek, ngerm +an, ngerman-x-2009-06-19, nynorsk, oriya, panjabi, pinyin, polish, portuguese, +romanian, russian, sanskrit, serbian, slovak, slovenian, spanish, swedish, swis +sgerman, tamil, telugu, turkish, turkmen, ukenglish, ukrainian, uppersorbian, u +senglishmax, welsh, loaded. +("C:\Program Files\MiKTeX 2.9\tex\latex\base\book.cls" +Document Class: book 2007/10/19 v1.4h Standard LaTeX document class +("C:\Program Files\MiKTeX 2.9\tex\latex\base\bk11.clo" +File: bk11.clo 2007/10/19 v1.4h Standard LaTeX file (size option) +) +\c@part=\count79 +\c@chapter=\count80 +\c@section=\count81 +\c@subsection=\count82 +\c@subsubsection=\count83 +\c@paragraph=\count84 +\c@subparagraph=\count85 +\c@figure=\count86 +\c@table=\count87 +\abovecaptionskip=\skip41 +\belowcaptionskip=\skip42 +\bibindent=\dimen102 +) +("C:\Program Files\MiKTeX 2.9\tex\latex\base\inputenc.sty" +Package: inputenc 2008/03/30 v1.1d Input encoding file +\inpenc@prehook=\toks14 +\inpenc@posthook=\toks15 + +("C:\Program Files\MiKTeX 2.9\tex\latex\base\utf8.def" +File: utf8.def 2008/04/05 v1.1m UTF-8 support for inputenc +Now handling font encoding OML ... +... no UTF-8 mapping file for font encoding OML +Now handling font encoding T1 ... +... processing UTF-8 mapping file for font encoding T1 + +("C:\Program Files\MiKTeX 2.9\tex\latex\base\t1enc.dfu" +File: t1enc.dfu 2008/04/05 v1.1m UTF-8 support for inputenc + defining Unicode char U+00A1 (decimal 161) + defining Unicode char U+00A3 (decimal 163) + defining Unicode char U+00AB (decimal 171) + defining Unicode char U+00BB (decimal 187) + defining Unicode char U+00BF (decimal 191) + defining Unicode char U+00C0 (decimal 192) + defining Unicode char U+00C1 (decimal 193) + defining Unicode char U+00C2 (decimal 194) + defining Unicode char U+00C3 (decimal 195) + defining Unicode char U+00C4 (decimal 196) + defining Unicode char U+00C5 (decimal 197) + defining Unicode char U+00C6 (decimal 198) + defining Unicode char U+00C7 (decimal 199) + defining Unicode char U+00C8 (decimal 200) + defining Unicode char U+00C9 (decimal 201) + defining Unicode char U+00CA (decimal 202) + defining Unicode char U+00CB (decimal 203) + defining Unicode char U+00CC (decimal 204) + defining Unicode char U+00CD (decimal 205) + defining Unicode char U+00CE (decimal 206) + defining Unicode char U+00CF (decimal 207) + defining Unicode char U+00D0 (decimal 208) + defining Unicode char U+00D1 (decimal 209) + defining Unicode char U+00D2 (decimal 210) + defining Unicode char U+00D3 (decimal 211) + defining Unicode char U+00D4 (decimal 212) + defining Unicode char U+00D5 (decimal 213) + defining Unicode char U+00D6 (decimal 214) + defining Unicode char U+00D8 (decimal 216) + defining Unicode char U+00D9 (decimal 217) + defining Unicode char U+00DA (decimal 218) + defining Unicode char U+00DB (decimal 219) + defining Unicode char U+00DC (decimal 220) + defining Unicode char U+00DD (decimal 221) + defining Unicode char U+00DE (decimal 222) + defining Unicode char U+00DF (decimal 223) + defining Unicode char U+00E0 (decimal 224) + defining Unicode char U+00E1 (decimal 225) + defining Unicode char U+00E2 (decimal 226) + defining Unicode char U+00E3 (decimal 227) + defining Unicode char U+00E4 (decimal 228) + defining Unicode char U+00E5 (decimal 229) + defining Unicode char U+00E6 (decimal 230) + defining Unicode char U+00E7 (decimal 231) + defining Unicode char U+00E8 (decimal 232) + defining Unicode char U+00E9 (decimal 233) + defining Unicode char U+00EA (decimal 234) + defining Unicode char U+00EB (decimal 235) + defining Unicode char U+00EC (decimal 236) + defining Unicode char U+00ED (decimal 237) + defining Unicode char U+00EE (decimal 238) + defining Unicode char U+00EF (decimal 239) + defining Unicode char U+00F0 (decimal 240) + defining Unicode char U+00F1 (decimal 241) + defining Unicode char U+00F2 (decimal 242) + defining Unicode char U+00F3 (decimal 243) + defining Unicode char U+00F4 (decimal 244) + defining Unicode char U+00F5 (decimal 245) + defining Unicode char U+00F6 (decimal 246) + defining Unicode char U+00F8 (decimal 248) + defining Unicode char U+00F9 (decimal 249) + defining Unicode char U+00FA (decimal 250) + defining Unicode char U+00FB (decimal 251) + defining Unicode char U+00FC (decimal 252) + defining Unicode char U+00FD (decimal 253) + defining Unicode char U+00FE (decimal 254) + defining Unicode char U+00FF (decimal 255) + defining Unicode char U+0102 (decimal 258) + defining Unicode char U+0103 (decimal 259) + defining Unicode char U+0104 (decimal 260) + defining Unicode char U+0105 (decimal 261) + defining Unicode char U+0106 (decimal 262) + defining Unicode char U+0107 (decimal 263) + defining Unicode char U+010C (decimal 268) + defining Unicode char U+010D (decimal 269) + defining Unicode char U+010E (decimal 270) + defining Unicode char U+010F (decimal 271) + defining Unicode char U+0110 (decimal 272) + defining Unicode char U+0111 (decimal 273) + defining Unicode char U+0118 (decimal 280) + defining Unicode char U+0119 (decimal 281) + defining Unicode char U+011A (decimal 282) + defining Unicode char U+011B (decimal 283) + defining Unicode char U+011E (decimal 286) + defining Unicode char U+011F (decimal 287) + defining Unicode char U+0130 (decimal 304) + defining Unicode char U+0131 (decimal 305) + defining Unicode char U+0132 (decimal 306) + defining Unicode char U+0133 (decimal 307) + defining Unicode char U+0139 (decimal 313) + defining Unicode char U+013A (decimal 314) + defining Unicode char U+013D (decimal 317) + defining Unicode char U+013E (decimal 318) + defining Unicode char U+0141 (decimal 321) + defining Unicode char U+0142 (decimal 322) + defining Unicode char U+0143 (decimal 323) + defining Unicode char U+0144 (decimal 324) + defining Unicode char U+0147 (decimal 327) + defining Unicode char U+0148 (decimal 328) + defining Unicode char U+014A (decimal 330) + defining Unicode char U+014B (decimal 331) + defining Unicode char U+0150 (decimal 336) + defining Unicode char U+0151 (decimal 337) + defining Unicode char U+0152 (decimal 338) + defining Unicode char U+0153 (decimal 339) + defining Unicode char U+0154 (decimal 340) + defining Unicode char U+0155 (decimal 341) + defining Unicode char U+0158 (decimal 344) + defining Unicode char U+0159 (decimal 345) + defining Unicode char U+015A (decimal 346) + defining Unicode char U+015B (decimal 347) + defining Unicode char U+015E (decimal 350) + defining Unicode char U+015F (decimal 351) + defining Unicode char U+0160 (decimal 352) + defining Unicode char U+0161 (decimal 353) + defining Unicode char U+0162 (decimal 354) + defining Unicode char U+0163 (decimal 355) + defining Unicode char U+0164 (decimal 356) + defining Unicode char U+0165 (decimal 357) + defining Unicode char U+016E (decimal 366) + defining Unicode char U+016F (decimal 367) + defining Unicode char U+0170 (decimal 368) + defining Unicode char U+0171 (decimal 369) + defining Unicode char U+0178 (decimal 376) + defining Unicode char U+0179 (decimal 377) + defining Unicode char U+017A (decimal 378) + defining Unicode char U+017B (decimal 379) + defining Unicode char U+017C (decimal 380) + defining Unicode char U+017D (decimal 381) + defining Unicode char U+017E (decimal 382) + defining Unicode char U+200C (decimal 8204) + defining Unicode char U+2013 (decimal 8211) + defining Unicode char U+2014 (decimal 8212) + defining Unicode char U+2018 (decimal 8216) + defining Unicode char U+2019 (decimal 8217) + defining Unicode char U+201A (decimal 8218) + defining Unicode char U+201C (decimal 8220) + defining Unicode char U+201D (decimal 8221) + defining Unicode char U+201E (decimal 8222) + defining Unicode char U+2030 (decimal 8240) + defining Unicode char U+2031 (decimal 8241) + defining Unicode char U+2039 (decimal 8249) + defining Unicode char U+203A (decimal 8250) + defining Unicode char U+2423 (decimal 9251) +) +Now handling font encoding OT1 ... +... processing UTF-8 mapping file for font encoding OT1 + +("C:\Program Files\MiKTeX 2.9\tex\latex\base\ot1enc.dfu" +File: ot1enc.dfu 2008/04/05 v1.1m UTF-8 support for inputenc + defining Unicode char U+00A1 (decimal 161) + defining Unicode char U+00A3 (decimal 163) + defining Unicode char U+00B8 (decimal 184) + defining Unicode char U+00BF (decimal 191) + defining Unicode char U+00C5 (decimal 197) + defining Unicode char U+00C6 (decimal 198) + defining Unicode char U+00D8 (decimal 216) + defining Unicode char U+00DF (decimal 223) + defining Unicode char U+00E6 (decimal 230) + defining Unicode char U+00EC (decimal 236) + defining Unicode char U+00ED (decimal 237) + defining Unicode char U+00EE (decimal 238) + defining Unicode char U+00EF (decimal 239) + defining Unicode char U+00F8 (decimal 248) + defining Unicode char U+0131 (decimal 305) + defining Unicode char U+0141 (decimal 321) + defining Unicode char U+0142 (decimal 322) + defining Unicode char U+0152 (decimal 338) + defining Unicode char U+0153 (decimal 339) + defining Unicode char U+2013 (decimal 8211) + defining Unicode char U+2014 (decimal 8212) + defining Unicode char U+2018 (decimal 8216) + defining Unicode char U+2019 (decimal 8217) + defining Unicode char U+201C (decimal 8220) + defining Unicode char U+201D (decimal 8221) +) +Now handling font encoding OMS ... +... processing UTF-8 mapping file for font encoding OMS + +("C:\Program Files\MiKTeX 2.9\tex\latex\base\omsenc.dfu" +File: omsenc.dfu 2008/04/05 v1.1m UTF-8 support for inputenc + defining Unicode char U+00A7 (decimal 167) + defining Unicode char U+00B6 (decimal 182) + defining Unicode char U+00B7 (decimal 183) + defining Unicode char U+2020 (decimal 8224) + defining Unicode char U+2021 (decimal 8225) + defining Unicode char U+2022 (decimal 8226) +) +Now handling font encoding OMX ... +... no UTF-8 mapping file for font encoding OMX +Now handling font encoding U ... +... no UTF-8 mapping file for font encoding U + defining Unicode char U+00A9 (decimal 169) + defining Unicode char U+00AA (decimal 170) + defining Unicode char U+00AE (decimal 174) + defining Unicode char U+00BA (decimal 186) + defining Unicode char U+02C6 (decimal 710) + defining Unicode char U+02DC (decimal 732) + defining Unicode char U+200C (decimal 8204) + defining Unicode char U+2026 (decimal 8230) + defining Unicode char U+2122 (decimal 8482) + defining Unicode char U+2423 (decimal 9251) +)) +("C:\Program Files\MiKTeX 2.9\tex\generic\babel\babel.sty" +Package: babel 2008/07/06 v3.8l The Babel package + +************************************* +* Local config file bblopts.cfg used +* +("C:\Program Files\MiKTeX 2.9\tex\latex\00miktex\bblopts.cfg" +File: bblopts.cfg 2006/07/31 v1.0 MiKTeX 'babel' configuration +) +("C:\Program Files\MiKTeX 2.9\tex\generic\babel\ngermanb.ldf" +Language: ngermanb 2008/07/06 v2.6n new German support from the babel system + +("C:\Program Files\MiKTeX 2.9\tex\generic\babel\babel.def" +File: babel.def 2008/07/06 v3.8l Babel common definitions +\babel@savecnt=\count88 +\U@D=\dimen103 +) +\l@naustrian = a dialect from \language\l@ngerman +Package babel Info: Making " an active character on input line 92. +)) +("C:\Program Files\MiKTeX 2.9\tex\latex\graphics\graphicx.sty" +Package: graphicx 1999/02/16 v1.0f Enhanced LaTeX Graphics (DPC,SPQR) + +("C:\Program Files\MiKTeX 2.9\tex\latex\graphics\keyval.sty" +Package: keyval 1999/03/16 v1.13 key=value parser (DPC) +\KV@toks@=\toks16 +) +("C:\Program Files\MiKTeX 2.9\tex\latex\graphics\graphics.sty" +Package: graphics 2009/02/05 v1.0o Standard LaTeX Graphics (DPC,SPQR) + +("C:\Program Files\MiKTeX 2.9\tex\latex\graphics\trig.sty" +Package: trig 1999/03/16 v1.09 sin cos tan (DPC) +) +("C:\Program Files\MiKTeX 2.9\tex\latex\00miktex\graphics.cfg" +File: graphics.cfg 2007/01/18 v1.5 graphics configuration of teTeX/TeXLive +) +Package graphics Info: Driver file: pdftex.def on input line 91. + +("C:\Program Files\MiKTeX 2.9\tex\latex\pdftex-def\pdftex.def" +File: pdftex.def 2010/09/14 v0.05b Graphics/color for pdfTeX +\Gread@gobject=\count89 +)) +\Gin@req@height=\dimen104 +\Gin@req@width=\dimen105 +) +("C:\Program Files\MiKTeX 2.9\tex\latex\listings\listings.sty" +\lst@mode=\count90 +\lst@gtempboxa=\box26 +\lst@token=\toks17 +\lst@length=\count91 +\lst@currlwidth=\dimen106 +\lst@column=\count92 +\lst@pos=\count93 +\lst@lostspace=\dimen107 +\lst@width=\dimen108 +\lst@newlines=\count94 +\lst@lineno=\count95 +\lst@maxwidth=\dimen109 + +("C:\Program Files\MiKTeX 2.9\tex\latex\listings\lstmisc.sty" +File: lstmisc.sty 2007/02/22 1.4 (Carsten Heinz) +\c@lstnumber=\count96 +\lst@skipnumbers=\count97 +\lst@framebox=\box27 +) +("C:\Program Files\MiKTeX 2.9\tex\latex\listings\listings.cfg" +File: listings.cfg 2007/02/22 1.4 listings configuration +)) +Package: listings 2007/02/22 1.4 (Carsten Heinz) + +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\RPG-X2 Lua Documentation Deu.aux") +LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 47. +LaTeX Font Info: ... okay on input line 47. +LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 47. +LaTeX Font Info: ... okay on input line 47. +LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 47. +LaTeX Font Info: ... okay on input line 47. +LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 47. +LaTeX Font Info: ... okay on input line 47. +LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 47. +LaTeX Font Info: ... okay on input line 47. +LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 47. +LaTeX Font Info: ... okay on input line 47. + +("C:\Program Files\MiKTeX 2.9\tex\context\base\supp-pdf.mkii" +[Loading MPS to PDF converter (version 2006.09.02).] +\scratchcounter=\count98 +\scratchdimen=\dimen110 +\scratchbox=\box28 +\nofMPsegments=\count99 +\nofMParguments=\count100 +\everyMPshowfont=\toks18 +\MPscratchCnt=\count101 +\MPscratchDim=\dimen111 +\MPnumerator=\count102 +\everyMPtoPDFconversion=\toks19 +) +\c@lstlisting=\count103 +LaTeX Font Info: External font `cmex10' loaded for size +(Font) <12> on input line 54. +LaTeX Font Info: External font `cmex10' loaded for size +(Font) <8> on input line 54. +LaTeX Font Info: External font `cmex10' loaded for size +(Font) <6> on input line 54. + [1 + + +{C:/ProgramData/MiKTeX/2.9/pdftex/config/pdftex.map}] [2 + +] +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\RPG-X2 Lua Documentation Deu.toc" +LaTeX Font Info: External font `cmex10' loaded for size +(Font) <10.95> on input line 3. + +[3] [4] [5]) +\tf@toc=\write3 + [6] +Kapitel 1. +LaTeX Font Info: Try loading font information for OMS+cmr on input line 65. +("C:\Program Files\MiKTeX 2.9\tex\latex\base\omscmr.fd" +File: omscmr.fd 1999/05/25 v2.5h Standard LaTeX font definitions +) +LaTeX Font Info: Font shape `OMS/cmr/m/n' in size <10.95> not available +(Font) Font shape `OMS/cmsy/m/n' tried instead on input line 65. + +Overfull \hbox (67.23381pt too wide) in paragraph at lines 67--68 +[]\OT1/cmr/m/n/10.95 Funktionsaufrufe der Lua Ba-sis Bi-blio-the-ken (Bei-spiel +: \OT1/cmr/bx/n/10.95 to-string(clientNum)\OT1/cmr/m/n/10.95 ). + [] + + +Overfull \hbox (28.80782pt too wide) in paragraph at lines 70--71 +\OT1/cmr/m/n/10.95 ers-te Ar-gu-ment ist \OT1/cmr/bx/n/10.95 var.function(var) +\OT1/cmr/m/n/10.95 k[]onnen auch als \OT1/cmr/bx/n/10.95 var:function() + [] + +[7 + +] [8 + +] +Kapitel 2. +[9] [10] [11] [12] +Kapitel 3. +[13 + +] [14 + +] +Kapitel 4. +[15] [16] [17] [18] [19] [20] [21] +Overfull \hbox (48.76537pt too wide) in paragraph at lines 473--476 +\OT1/cmr/bx/n/10.95 ent.SetKeyValue(\OT1/cmr/m/it/10.95 entity \OT1/cmr/bx/n/10 +.95 ent, \OT1/cmr/m/it/10.95 string \OT1/cmr/bx/n/10.95 key, \OT1/cmr/m/it/10.9 +5 string \OT1/cmr/bx/n/10.95 va-lue) \OT1/cmr/m/n/10.95 or \OT1/cmr/bx/n/10.95 +ent:SetKeyValue(\OT1/cmr/m/it/10.95 string + [] + +[22] [23] [24] [25] +Overfull \hbox (0.82597pt too wide) in paragraph at lines 602--605 +\OT1/cmr/bx/n/10.95 mover.SetAngles(\OT1/cmr/m/it/10.95 entity \OT1/cmr/bx/n/10 +.95 ent, \OT1/cmr/m/it/10.95 vec-tor \OT1/cmr/bx/n/10.95 angles) \OT1/cmr/m/n/1 +0.95 or \OT1/cmr/bx/n/10.95 mo-ver.SetAngles(\OT1/cmr/m/it/10.95 entity + [] + + +Overfull \hbox (1.09969pt too wide) in paragraph at lines 607--610 +\OT1/cmr/bx/n/10.95 mover.SetPosition(\OT1/cmr/m/it/10.95 entity \OT1/cmr/bx/n/ +10.95 ent, \OT1/cmr/m/it/10.95 vec-tor \OT1/cmr/bx/n/10.95 pos) \OT1/cmr/m/n/10 +.95 or \OT1/cmr/bx/n/10.95 mo-ver.SetPosition(\OT1/cmr/m/it/10.95 entity + [] + + +Overfull \hbox (54.56422pt too wide) in paragraph at lines 612--615 +\OT1/cmr/bx/n/10.95 mover.ToAngles\OT1/cmr/m/n/10.95 (\OT1/cmr/m/it/10.95 entit +y \OT1/cmr/bx/n/10.95 ent, \OT1/cmr/m/it/10.95 float \OT1/cmr/bx/n/10.95 speed, + \OT1/cmr/m/it/10.95 vec-tor \OT1/cmr/bx/n/10.95 angles) \OT1/cmr/m/n/10.95 or +\OT1/cmr/bx/n/10.95 mo-ver.ToAngles(\OT1/cmr/m/it/10.95 entity + [] + +[26] [27] +Underfull \hbox (badness 10000) in paragraph at lines 628--632 + + [] + +[28] +Kapitel 5. +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/HelloWorldGame.lua") +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/HelloWorldClient.lua" +Overfull \hbox (1.35376pt too wide) in paragraph at lines 3--4 +[][][][][][][][][][][][][][][] + [] + + +Overfull \hbox (1.35376pt too wide) in paragraph at lines 5--6 +[][][][][][][][][][][][][][][] + [] + + +Overfull \hbox (1.35376pt too wide) in paragraph at lines 7--8 +[][][][][][][][][][][][][][][] + [] + +) [29 + +] +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/HelloWorldClient1.lua") +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/HelloWorldClient2.lua") +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/HelloWorldClient3.lua") +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/HelloWorldClientAll.lua") +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/FindingEnts1.lua" [30]) +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/FindingEnts2.lua") +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/FindingEnts3.lua") +Overfull \hbox (9.8063pt too wide) in paragraph at lines 707--708 +\OT1/cmr/m/n/10.95 Dar[]uber hin-aus kann man ei-ni-ge En-ti-ties spaw-nen die +Brush Mo-dells ben[]otigen + [] + +[31] ("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/SpawningEnts.lua") +[32] +Underfull \hbox (badness 10000) in paragraph at lines 710--712 + + [] + + +Underfull \hbox (badness 10000) in paragraph at lines 715--717 + + [] + + +Underfull \hbox (badness 10000) in paragraph at lines 720--722 + + [] + + +Underfull \hbox (badness 10000) in paragraph at lines 725--727 + + [] + + +Underfull \hbox (badness 10000) in paragraph at lines 730--732 + + [] + +[33] [34 + +] +Kapitel 6. +[35] ("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/fusToff.lua" +Overfull \hbox (7.92383pt too wide) in paragraph at lines 5--6 +[][][][][][][][][][][][][][][][][] + [] + +) ("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/fusToff2.lua" +Overfull \hbox (7.92383pt too wide) in paragraph at lines 5--6 +[][][][][][][][][][][][][][][][][] + [] + +) [36] +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\RPG-X2 Lua Documentation Deu.aux") +) +Here is how much of TeX's memory you used: + 3541 strings out of 494019 + 46593 string characters out of 3146494 + 117528 words of memory out of 3000000 + 6792 multiletter control sequences out of 15000+200000 + 10722 words of font info for 37 fonts, out of 3000000 for 9000 + 714 hyphenation exceptions out of 8191 + 28i,8n,40p,502b,1642s stack positions out of 5000i,500n,10000p,200000b,50000s + +Output written on "RPG-X2 Lua Documentation Deu.pdf" (36 pages, 221543 bytes). +PDF statistics: + 165 PDF objects out of 1000 (max. 8388607) + 0 named destinations out of 1000 (max. 500000) + 1 words of extra memory for PDF output out of 10000 (max. 10000000) + diff --git a/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation Deu.pdf b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation Deu.pdf new file mode 100644 index 0000000..50e1452 Binary files /dev/null and b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation Deu.pdf differ diff --git a/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation Deu.tex b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation Deu.tex new file mode 100644 index 0000000..1d0b768 --- /dev/null +++ b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation Deu.tex @@ -0,0 +1,768 @@ +\documentclass[11pt,a4paper]{book} +\usepackage[utf8]{inputenc} +\usepackage[ngerman]{babel} +\usepackage{graphicx} +\usepackage{listings} +\usepackage{listings} +% -*- latex -*- +% Definition of the Lua language for the listings package +% Time-stamp: <2008-11-30 15:27:16 rsmith> +% Written by Roland Smith and hereby placed in the public +% domain. +\lstdefinelanguage{lua} + {morekeywords={and,break,do,else,elseif,end,false,for,function,if,in,local, + nil,not,or,repeat,return,then,true,until,while}, + morekeywords={[2]arg,assert,collectgarbage,dofile,error,_G,getfenv, + getmetatable,ipairs,load,loadfile,loadstring,next,pairs,pcall,print, + rawequal,rawget,rawset,select,setfenv,setmetatable,tonumber,tostring, + type,unpack,_VERSION,xpcall}, + morekeywords={[2]coroutine.create,coroutine.resume,coroutine.running, + coroutine.status,coroutine.wrap,coroutine.yield}, + morekeywords={[2]module,require,package.cpath,package.load,package.loaded, + package.loaders,package.loadlib,package.path,package.preload, + package.seeall}, + morekeywords={[2]string.byte,string.char,string.dump,string.find, + string.format,string.gmatch,string.gsub,string.len,string.lower, + string.match,string.rep,string.reverse,string.sub,string.upper}, + morekeywords={[2]table.concat,table.insert,table.maxn,table.remove, + table.sort}, + morekeywords={[2]math.abs,math.acos,math.asin,math.atan,math.atan2, + math.ceil,math.cos,math.cosh,math.deg,math.exp,math.floor,math.fmod, + math.frexp,math.huge,math.ldexp,math.log,math.log10,math.max,math.min, + math.modf,math.pi,math.pow,math.rad,math.random,math.randomseed,math.sin, + math.sinh,math.sqrt,math.tan,math.tanh}, + morekeywords={[2]io.close,io.flush,io.input,io.lines,io.open,io.output, + io.popen,io.read,io.tmpfile,io.type,io.write,file:close,file:flush, + file:lines,file:read,file:seek,file:setvbuf,file:write}, + morekeywords={[2]os.clock,os.date,os.difftime,os.execute,os.exit,os.getenv, + os.remove,os.rename,os.setlocale,os.time,os.tmpname}, + sensitive=true, + morecomment=[l]{--}, + morecomment=[s]{--[[}{]]--}, + morestring=[b]", + morestring=[d]' + } +\lstset{numbers=left, numberstyle=\tiny, numbersep=5pt} +\lstset{language=lua} +\begin{document} +\title{ +\Huge RPG-X2 Lua Dokumentation +\author{ +Ubergames +Walter Julius 'GSIO01' Hennecke} +} +\maketitle +\newpage +\tableofcontents +\chapter{Einführung} +\label{intro} +\section{Grundlegende Informationen} +\label{gen-info} +Die RPG-X2 Lua Dokumentation dokumentiert und beschreibt alle Lua Funktionen die in RPG-X2 verfügbar sind. Die version die Sie gerade lesen ist für die \textbf{RPG-X2 Version 2.2 beta 4.4.5}. Die RPG-X2 Lua Dokumentation wird mit dem Erscheinen jeder neuen RPG-X Version aktualisiert. +\section{Vorvereinbarungen} +\label{preq} +\begin{itemize} + \item In Lua werden Variablen nicht mit ihrem Typ deklariert. Um Sie dennoch über den Typ einer Variablen informieren zu können werden die Typangaben Kursiv vor die Variablen geschrieben (Beispiel: \textit{integer} \textbf{clientNum}). + \item Es gibt in RPG-X2 Lua drei verschiedene Funktionsaufrufe.\begin{itemize} + \item Funktionsaufrufe der Lua Basis Bibliotheken (Beispiel: \textbf{\textbf{tostring(clientNum)}}). + \item Funktionsaufrufe der RPG-X2 Bibliotheken, welche den Bibliotheksnamen vorangestellt haben (Beispiel: \textbf{entity.Spawn()}). + \item Funktionsaufrufe über Variablen. Dies geht zum Beispiel mit Entities und Vectoren (Beispiel: \textbf{ent.Remove(ent)}). + \item Funktionsaufrufe über Variablen bei denen die Variable selber das erste Argument ist \textbf{var.function(var)} können auch als \textbf{var:function()} geschrieben werden (Beispiel: \textbf{ent.Remove(ent)} is the same as \textbf{ent:Remove()}). +\end{itemize} +\end{itemize} +\chapter{Lua Hooks} +\label{lua-hooks} +\section{Was ist ein Lua Hook} +\label{wia-lh} +Ein Lua Hook ist eine Funktion die aufgerufen wird wenn ein spezifisches Ereignis in der Spiellogik eintritt. So wird zum Beispiel beim initialisieren der Spiellogik die Funktion G\_InitGame aufgerufen. Diese Funktion besitzt einen Lua Hook, was bedeutet das beim Aufruf von G\_InitGame auch eine Lua Funktion aufgerufen wird. In RPG-X2 Lua gibt es Lua Hooks mit festen und Lua Hooks mit dynamischen Namen. +\section{Statische Lua Hooks} +\label{s-lh} +Statische Lua Hooks haben einen festen Namen. +\subsection{InitGame} +\label{init-game} +\textbf{InitGame(}\textit{integer} \textbf{leveltime,} \textit{integer} \textbf{randomssed, }\textit{integer} \textbf{restart)} +\newline +Wird nach dem Spielstart oder nach dem benutzen des map\_restart Kommandos aufgerufen. +\newline +\textbf{leveltime} die aktuelle Levelzeit in Millisekunden +\newline +\textbf{restart} ist 1 bei einem map\_restart und ansonsten 0 +\subsection{ShutdownGame} +\label{shutdown-game} +\textbf{ShutdownGame(}\textit{integer} \textbf{restart)} +\newline +Wird beim Beenden des Spiels aufgerufen, dies kann sein bei disconnect, schließen des Spiels, Map wechsel, map\_restart. +\newline +\textbf{restart} ist 1 bei einem map\_restart und ansonsten 0 +\subsection{RunFrame} +\label{run-frame} +\textbf{RunFrame(}\textit{integer} \textbf{leveltime)} +\newline +Wird jeden Frame aufgerufen. Diese Funktion sollte wenn dann nur mit äußerster Vorsicht verwendet werden, da Zeit zwischen Frames nur 50 ms beträgt. +\newline +\textbf{leveltime} aktuelle Levelzeit in Millisekunden +\subsection{GClientPrint} +\label{cli-print} +\textbf{GClientPrint(}\textit{string} \textbf{text, }\textit{entity }\textbf{client)} +\newline +Wird beim Aufruf der Spiellogikfunktion G\_PrintfClient aufgerufen. +\newline +\textbf{text} der ausgegeben wird +\newline +\textbf{client} Client bei dem der Text ausgegeben wird. +\subsection{GPrint} +\label{g-print} +\textbf{GPrint(}\textit{string}\textbf{ text)} +\newline +Wird beim Aufruf der Spiellogikfunktion G\_Print aufgerufen. +\newline +\textbf{text} der in die Spielkonsole ausgegeben wird. (Achtung die Spielkonsole ist die des Servers nicht die des Client!) +\newpage +\section{Dynamische Lua Hooks} +\label{dyn-lh} +Diese Lua Hook können verschiedene Namen haben. Sie sind alle Lua Hooks für Entities. Die Funktionenamen für diese Lua Hooks werden im Radiant mit Hilfe von Schlüssel-Wert-Paaren definiert. +Da die Funktionsnamen dieser Lua Hooks von diesen Paaren abhängen werden in dieser Dokumentation die Schlüssel als Funktionsnamen verwendet, die den Namen der entsprechenden Funktion enthalten. +\subsection{luaThink} +\label{luaThink} +\textbf{luaThink(}\textit{entity}\textbf{ ent)} +\newline +Wird jedes mal aufgerufen wenn die Entity denkt. +\newline +\textbf{ent} die Entity selbst +\subsection{luaTouch} +\label{luaTouch} +Wird jedes mal aufgerufen wenn die Entity berührt wird. +\newline +\textbf{ent} die Entity selbst +\newline +\textbf{other} die Entity welche die Entity \textbf{ent} berührt hat. +\subsection{luaUse} +\label{luaUse} +\textbf{luaUse(}\textit{entity}\textbf{ ent),}\textit{entity} \textbf{other,} \textit{entity} \textbf{activator)} +\newline +Wird jedes mal aufgerufen wenn die Entity benutzt wird. +\newline +\textbf{ent} die entity selbst +\newline +\textbf{activator} die Entity welche die Entity \textbf{ent} benutzt hat. +\subsection{luaHurt} +\label{luaHurt} +\textbf{luaHurt(}\textit{entity}\textbf{ ent, }\textit{entity}\textbf{ inflictor,}\textit{entity}\textbf{attacker)} +\newline +Wird jedes mal aufgerufen wenn der Entity Schaden zugefügt wird. +\newline +\textbf{ent} die Entity selbst +\newline +\textbf{inflictor} der Infliktor +\newline +\textbf{attacker} der Angreifer +\subsection{luaDie} +\label{luaDie} +\textbf{luaDie(}\textit{entity} \textbf{ent,} \textit{entity} \textbf{inflictor,} \textit{entity} \textbf{attacker,} \textit{integer} \textbf{dmg,} \textit{integer}\textbf{ mod)} +\newline +Wird jedes mal aufgerufen wenn die Entiy stirbt. +\newline +\textbf{ent} die Entity selbst +\newline +\textbf{inflictor} der Infliktor +\newline +\textbf{attacker} der Angreifer +\newline +\textbf{dmg} menge des Schadens +\newline +\textbf{mod} Gründe des Todes +\subsection{luaFree} +\label{luaFree} +\textbf{luaFree(}\textit{entity} \textbf{ent)} +\newline +Wird aufgerufen wenn die Entity freigegeben wird was gleichbedeutend mit deren Löschung ist. +\newline +\textbf{ent} die Entity selbst +\subsection{luaReached} +\label{luaReached} +\textbf{luaReached(}\textit{entity }\textbf{ent)} +\newline +Wird aufgerufen wenn die Bewegung einer Entity ihren Endpunkt erreicht. +\newline +\textbf{ent} die Entity selbst +\subsection{luaReachedAngular} +\label{luaReachedAngular} +\textbf{luaReachedAngular(}\textit{entity }\textbf{ent)} +\newline +Wird aufgerufen wenn die Drehbewegung einer Entity ihren Endpunkt erreicht. +\newline +\textbf{ent} die Entity selbst +\subsection{luaTrigger} +\label{luaTrigger} +\textbf{luaTrigger(}\textit{entity}\textbf{ ent, }\textit{entity}\textbf{ other)} +\newline +Wird aufgerufen wenn eine Entity getriggered wird. Man beachte das dies nicht das gleiche ist wie die Benutzung einer Entity. Dieser Lua Hook ist für trigger\_* Entities gedacht. +\newline +\textbf{ent} die Entity selbst +\newline +\textbf{other} die Entity die das Triggerereignis ausgelöst hat +\subsection{luaSpawn} +\label{luaSpawn} +\textbf{lauSpawn(}\textit{entity}\textbf{ ent)} +\newline +Wird aufgerufen wenn die Spawnfunktion einer Entity aufgerufen wird. +\newline +\textbf{ent} die Entity selbst +\newpage +\chapter{RPG-X2 Map Scripting} +\label{rpgx2-mapscripting} +\section{Map scripts} +\label{map-scripts} +Im moment kann genau ein Scriptdatei je Map geladen werden. Diese Scriptdatei muss im Ordner \textit{scripts/lua/} sein und den Namen \textit{.lua} haben. +\section{Aufruf von Funktionen} +\label{map-callingfunction} +Es gibt dynamische Lua Hooks für die Verwendung im Radiant (siehe Dynamische Lua Hooks). Sie könne diese Lua Hooks für Entites verwenden, in dem Sie das entsprechende Schlüssel-Wert-Paar zu der Entity hinzufügen. +\newline +Soll zum Beispiel eine Funktion \textit{PrintText} aufgerufen werden wenn eine \textit{func\_usable} benutzt wird so müssen die zu der Entity die Schlüssel \textit{luaUse} und den Wrt \textit{PrintText} hinzufügen. +\newpage +\chapter{RPG-X2 Lua Bibliotheken} +\label{rpgx2-llibs} +\section{game} +\label{g} +Diese Bibliothek ermöglicht Zugriff auf einige Spiellogikfunktionen wie zum Beispiel G\_Printf und G\_ClientPrintf. +\subsection{game.Print} +\label{g-prnt} +\textbf{game.Print(}\textit{string}\textbf{ text)} +\newline +Gibt \textbf{text} in der Spielkonsole(Serverkonsole) aus. +\subsection{game.ClientPrint} +\label{g-clientprint} +\textbf{game.ClientPrint(}\textit{integer}\textbf{ clientNum, }\textit{string}\textbf{ text)} +\newline +Gibt \textbf{text} in der Konsole tes Client mit der Clientnummer \textbf{clientNum} aus. Falls \textbf{clientNum} gleich -1 ist wird der Text in den Konsolen aller Spieler ausgegeben. +\subsection{game.CenterPrint} +\label{g-centerprint} +\textbf{game.CenterPrint(}\textit{integer}\textbf{ clientNum, }\textit{string}\textbf{ text)} +\newline +Gibt \textbf{text} auf der Mitte des Bildschirmes des Spielers mit der Clientnummer \textbf{clientNum} aus. Falls \textbf{clientNum} gleich -1 ist erfolgt die Ausgabe bei allen Spielern. +\subsection{game.MessagePrint} +\label{g-messagepritn} +\textbf{game.MessagePrint(}\textit{integer}\textbf{ clientNum, }\textit{string}\textbf{ text)} +\newline +Gibt \textbf{text} in der rechten unteren Ecke des Bildschirmes des Spielers mit der Clientennummer \textbf{clientNum} aus. Falls \textbf{clientNum} gleicht -1 ist erfolgt die Ausgabe bei allen Spielern. +\subsection{game.LevelTime} +\label{g-leveltime} +\textbf{game.LevelTime()} +Gibt die aktuelle Levelzeit in Millisekunden zurück. +\subsection{game.SetGlobal} +\label{g-setglobal} +\textbf{game.SetGlobal(}\textit{string}\textbf{ name, value)} +\newline +Setzt eine globale Lua Variable mit den Namen \textbf{name} und dem Wert \textbf{value}, welche dann über alle Funktionen hinweg verfügbar ist. +\subsection{game.GetGlobal} +\label{g-getglobal} +\textbf{game.GetGlobal(}\textit{string}\textbf{ name)} +\newline +Gibt den Wert eine globalen Variable mit dem Namen \textbf{name} zurück falls diese existiert. +\newpage +\section{qmath} +\label{qmath} +Diese Bibliothek ermöglicht den Zugriff auf mehrere mathematische Funktionen die in der Spiellogik verfügbar sind. +\subsection{qmath.abs} +\label{qm-abs} +\textbf{qmath.abs(}\textit{float}\textbf{ f)} +\newline +Gibt den ganzzahligen Wert von \textbf{f} zurück. +\subsection{qmath.sin} +\label{qm-sin} +\textbf{qmath.sin(}\textit{float}\textbf{ degree)} +\newline +Gibt den sinus von \textbf{degree} zurück. +\subsection{qmath.cos} +\label{qm-cos} +\textbf{qmath.cos(}\textit{float}\textbf{ degree)} +\newline +Gibt den cosinus von \textbf{degree} zurück. +\subsection{qmath.tan} +\label{qm-tan} +\textbf{qmath.tan(}\textit{float}\textbf{ degree)} +\newline +Gibt den tangenz von \textbf{degree} zurück. +\subsection{qmath.asin} +\label{qm-asin} +\textbf{qmath.asin(}\textit{float}\textbf{ f)} +\newline +Gibt den arsinus von \textbf{f} zurück. +\subsection{qmath.acos} +\label{qm-acos} +\textbf{qmath.acos(}\textit{float}\textbf{ f)} +\newline +Gibt den arcosinus von \textbf{f} zurück. +\subsection{qmath.atan} +\label{qm-atan} +\textbf{qmath.atan(}\textit{float}\textbf{ f)} +\newline +Gibt den artangenz von \textbf{f} zurück. +\subsection{qmath.floor} +\label{qm-floor} +\textbf{qmath.floor(}\textit{float}\textbf{ f)} +\newline +Gibt den abgerundeten Wert von \textbf{f} zurück. +\subsection{qmath.ceil} +\label{qm-ceil} +\textbf{qath.ceil(}\textit{float}\textbf{ f)} +\newline +Gibt den aufgerundeten Wert von \textbf{f} zurück. +\subsection{qmath.fmod} +\label{qm-fmod} +\textbf{qmath.fmod(}\textit{float}\textbf{ f, }\textit{float}\textbf{ n)} +\newline +Gibt den Rest von \begin{math}f/n.\end{math} zurück. +\subsection{qmath.modf} +\label{qm-modf} +\textbf{qmath.modf(}\textit{float}\textbf{ f)} +\newline +Zerlegt \begin{math}f\end{math} in einen ganzzahligen und einen fraktionalen Teil. Der fraktionale Teil wird zurückgegeben und der ganzzahlige Teil in \begin{math}f\end{math} gespeichert. +\subsection{qmath.sqrt} +\label{qm-sqrt} +\textbf{qmath.sqrt(}\textit{float}\textbf{ f)} +\newline +Gibt die Wurzel aus \textbf{f} zurück. +\subsection{qmath.log} +\label{qm-log} +\textbf{qmath.log(}\textit{float}\textbf{ f)} +\newline +Gibt den Logarithmus von \textbf{f} zurück. +\subsection{qmath.log10} +\label{qm-log10} +\textbf{qmath.log10(}\textit{float}\textbf{ f)} +\newline +Gibt den Logarithmus von \textbf{f} zur Basis 10 zurück. +\subsection{qmath.deg} +\label{qm-deg} +\textbf{qmath.deg(}\textit{float}\textbf{ radian)} +\newline +Konvertiert Bogenmaß zu grad. +\subsection{qmath.rad} +\label{qm-rad} +\textbf{qmath.rad(}\textit{float}\textbf{ degree)} +\newline +Konvertiert Grad zu Bogenmaß. +\subsection{qmath.frexp} +\label{qm-frexp} +\textbf{qmath.frexp(}\textit{float}\textbf{ f)} +\newline +Zerlegt \textbf{f} in seine binäre Signifikante und einen integralen Exponenten von 2, so dass gilt: +\newline +\begin{math}x=Signifikante*2^Exponent\end{math} +\subsection{qmath.ldexp} +\label{qm-ldexp} +\textbf{qmath.ldexp(}\textit{float}\textbf{ f, }\textit{float}\textbf{ n)} +\newline +Gibt das Resultat der Multiplikation von \textbf{f} mit 2 potenziert mit \textbf{n} zurück. +\subsection{qmath.min} +\label{qm-min} +\textbf{qmath.min(}\textit{integer}\textbf{ array[])} +\newline +Gibt \textbf{array[]}. +\subsection{qmath.max} +\label{qm-max} +\textbf{qmath.max(}\textit{integer}\textbf{ array[])} +\newline +Gibt den höchsten Wert aus \textbf{array[]} zurück. +\subsection{qmath.random} +\label{qm-random} +\textbf{qmath.random()} +\newline +Gibt zufällige ganzzahlige Werte zurück. +\subsection{qmath.crandom} +\label{qm-crandom} +\textbf{qmath.crandom()} +\newline +Gibt zufällige Gleitkommazahlen zurück (Bei der Generierung wird die sogenannte crazy random function verwendet). +\newpage +\section{vector} +\label{vect} +Diese Bibliothek implementiert einen neuen Variablentyp vector sowie mathematische Funktonen zum Rechnen mit Vektoren. +\subsection{vector.New} +\label{vect-new} +\textbf{vector.New()} +\newline +Erzeugt einen neuen Vektor \begin{math}\left(\begin{array}{c} 0 \\ 0 \\ 0 \\ \end{array}\right)\end{math}. +\subsection{vector.Construct} +\label{vect-cons} +\textbf{vector.Construct(}\textit{float}\textbf{ x, }\textit{float}\textbf{ y, }\textit{float}\textbf{ z)} +\newline +Erzeugt einen neuen Vektor \begin{math}\left(\begin{array}{c} x \\ y \\ z \\ \end{array}\right)\end{math}. +\subsection{vector.Set} +\label{vect-set} +\textbf{vector.Set(}\textit{vector}\textbf{ v, }\textit{float}\textbf{ x, }\textit{float}\textbf{ y, }\textit{float}\textbf{ z)} +\newline +Setzt einen Vektor \textbf{v} auf die angegeben Werte. +\subsection{vector.clear} +\label{vect-clear} +\textbf{vector.Clear(}\textit{vector}\textbf{ v)} +\newline +Säubert einen \textbf{vector} indem er auf \begin{math}\left(\begin{array}{c} 0 \\ 0 \\ 0 \\ \end{array}\right)\end{math} gesetzt wird. +\subsection{vector.Add} +\label{vect-add} +\textbf{vector.Add(}\textit{vector}\textbf{ a, }\textit{vector}\textbf{ b, }\textit{vector}\textbf{ c)} +\newline +Bildet die Summer von \textbf{a} und \textbf{b} und speichert das Ergebnis in \textbf{c}. +\subsection{vector.Substract} +\label{vect-sub} +\textbf{vector.Subtract(}\textit{vector}\textbf{ a, }\textit{vector}\textbf{ b, }\textit{vector}\textbf{ c)} +\newline +Subtrahiert \textbf{b} von \textbf{a} und speichert das Ergebis in \textbf{c}. +\subsection{vector.Scale} +\label{vect-scale} +\textbf{vector.Scale(}\textit{vector}\textbf{ a, }\textit{float}\textbf{ b, }\textit{vector}\textbf{ c)} +\newline +Skaliert \textbf{a} um den Faktor \textbf{b} und speichert das Ergebnis in \textbf{c}. +\subsection{vector.Length} +\label{vect-length} +\textbf{vector.Length(}\textit{vector}\textbf{ a)} +\newline +Gibt die Länge von \textbf{a} zurück. +\subsection{vector.Normalize} +\label{vect-norm} +\textbf{vector.Normalize(}\textit{vector}\textbf{ a)} +\newline +Normalisiert \textbf{a}. +\subsection{vector.RotateAroundPoint} +\label{vect-rotarndpnt} +\textbf{vector.RotateAroundPoint(}\textit{vector}\textbf{ dest, }\textit{vector}\textbf{ dir, }\textit{vector}\textbf{ point, }\textit{float}\textbf{ degrees)} +\newline +Rotiert einen Punkt \textbf{point} um einen gegebene Vektor. +\newline +\textbf{dir} Normalisierter Vektor um den rotiert werden soll. +\newline +\textbf{point} Punkt der rotiert werden soll +\newline +\textbf{degrees} um wieviel Grad rotiert werden soll +\newline +\textbf{dst} der Punkt point nach der Drehung +\subsection{vector.Perpendicular} +\label{vect-Perpendicular} +\textbf{vector.Perpendicular(}\textit{vector}\textbf{ dest, }\textit{vector}\textbf{ src)} +\newline +Findet einen zum Quellvektor senkrechten Zielvektor. +\textbf{src} Quellvektor +\textbf{dest} Ein zum Quellvektor senkrechter Vektor (das Ergebnis) +\newpage +\section{entity} +\label{enty} +Diese Bibliothek enthält Funktionen für Entities. Alle Funktionen mit vorangestellten \textit{entity} sind Funktionsaufrufe über dei Bibliothek. Alle Funktionen mit vorangestellten \textit{ent} sind Funktionsaufrufe auf einer Entity. +\subsection{entity.Find} +\label{enty-find} +\textbf{entity.Find(}\textit{string}\textbf{ targetname)} +\newline +Gibt die erste gefundene Entity zurück deren targetname dem gesuchten targetname entspricht. +\subsection{entity.FindNumber} +\label{enty.findnumber} +\textbf{entity.FindNumber(}\textit{integer}\textbf{ entnum)} +\newline +Gibt die Entity mit der Entitynummer\textbf{entnum} zurück. +\subsection{entity.FindBModel} +\label{enty-findbmodel} +\textbf{entity.FindBModel(}\textit{integer}\textbf{ bmodelnum)} +\newline +Gibt die Entity mit dem brush model *\textbf{bmodelnumber} zurück. Dies ist der einzig sichere weg eine Brushentity ohne targetname zu finden, da sich de Entitynummern abhängig davon verändern ob eine Map lokal geladen wird, auf einen lokalen server läuft oder auf einem dedizierten Internetserver läuft ändern. +\subsection{ent.GetNumber} +\label{enty-getnumber} +\textbf{ent.GetNumber(}\textit{entity}\textbf{ ent)} or \textbf{ent:GetNumber()} +\newline +Gibt die Entitynummer einer Entity zurück. +\subsection{ent.SetKeyValue} +\label{enty-setkeyvalue} +\textbf{ent.SetKeyValue(}\textit{entity}\textbf{ ent, }\textit{string}\textbf{ key, }\textit{string}\textbf{ value)} or \textbf{ent:SetKeyValue(}\textit{string}\textbf{ key, }\textit{string}\textbf{ value)} +\newline +Setzt ein Schlüssel-Wert-Paar für \textbf{ent} wie im Radiant. Dies Funktioniert aber nur wenn der Schlüssel \textit{key} ein Teil von \textit{fields\_t} ist, welche die vordefinierten Schlüssel enthält. +\subsection{entity.Remove} +\label{enty-remove} +\textbf{entity.Remove(}\textit{entity}\textbf{ ent)} +\newline +Entfernt die Entity \textbf{ent}. +\subsection{ent.GetOrigin} +\label{enty-getorigin} +\textbf{ent.GetOrigin(}\textit{entity}\textbf{ ent)} or \textbf{ent:GetOrigin()} +\newline +Gibt die Origin von \textbf{ent} als vector zurück. +\subsection{ent.IsClient} +\label{enty-isclient} +\textbf{ent.IsClient(}\textit{entity}\textbf{ ent)} or \textbf{ent:IsClient()} +\newline +Gibt einen boolean zurück. Ist wahr wenn \textbf{ent} ein Spieler ist. +\subsection{ent.GetClientname} +\label{enty-getclientname} +\textbf{ent.GetClientname(}\textit{entity}\textbf{ ent)} or \textbf{ent:GetClientname()} +\newline +Gibt den clientname von \textbf{ent} zurück. +\subsection{ent.GetClassname} +\label{enty-getclassname} +\textbf{ent.GetClassname(}\textit{entity}\textbf{ ent)} or \textbf{ent:GetClassname()} +\newline +Gibt den Klassennamen von \textbf{ent} zurück. +\subsection{ent.SetClassname} +\label{enty-setclassname} +\textbf{ent.SetClassname(}\textit{entity}\textbf{ ent, }\textit{string}\textbf{ classname)} or +\newline +\textbf{ent:SetClassname(}\textit{string}\textbf{ classname)} +\newline +Setzt den Klassennamen von \textbf{ent} auf \textbf{classname}. +\subsection{ent.GetTargetname} +\label{enty-gettargetname} +\textbf{ent.GetTargetname(}\textit{entity}\textbf{ ent)} or \textbf{ent:GetTargetname()} +\newline +Gibt den targetname von\textbf{ent} zurück. +\subsection{ent.SetupTrigger} +\label{enty-setuptrigger} +\textbf{ent.SetupTrigger(}\textit{enttiy}\textbf{ ent)} or \textbf{ent:SetupTrigger()} +\newline +Grundlegende Initialisierung für im Scripting gespawnte trigger\_* Entities. +\subsection{entity.GetTarget} +\label{enty-gettarget} +\textbf{entity.GetTarget(}\textit{entity}\textbf{ ent)} +Gibt das target von \textbf{ent} zurück. +\subsection{entity.Use} +\label{enty-use} +\textbf{entity.Use(}\textit{entity}\textbf{ ent)} +\newline +Benutzt \textbf{ent}. +\subsection{entity.Spawn} +\label{enty-spawn} +\textbf{entity.spawn()} +\newline +Versucht eine neue Entity zu spawnen und gibt diese bei Erfolg zurück, sonst wird \textit{nil} zurückgegeben. +\subsection{entiy.CallSpawn} +\label{enty-callspawn} +\textbf{entity.CallSpawn(}\textit{entity}\textbf{ ent)} +\newline +Ruft die Spawnfunktion der Spiellogik für \textbf{ent} auf. +\subsection{entity.DelayedCallSpawn} +\label{enty-delayedcallspawn} +\textbf{entity.DelayedCallSpawn(}\textit{entity}\textbf{ ent, }\textit{integer}\textbf{ delay)} +\newline +Tut das gleiche wie CallSpawn wartet aber eine mit \textbf{delay} festgelegte Zeit bis die Funktion aufgerufen wird. +\textbf{delay} Zeit in Millisekunden die gewartet werden soll. +\subsection{entity.RemoveSpawns} +\label{enty-removespawns} +\textbf{entity.RemoveSpawns()} +\newline +Löscht alle Spawnpunkte der Map. +\subsection{ent.Lock} +\label{enty-lock} +\textbf{ent.Lock(}\textit{entity}\textbf{ ent)} +\newline +Sperrt/Verschließt die Entity. Funktioniert mit allen Entities die gesperrt/verschlossen werden können (Türen, Turbolifte, Usables, ...). +\subsection{ent.Unlock} +\label{enty-unlock} +\textbf{ent.Unlock(}\textit{entity}\textbf{ ent)} +\newline +Entsperrt/Öffnet die Entity. Funktioniert mit allen Entities die gespettz/verschlossen werden können (Türen, Turbolifte, Usables, ...). +\subsection{ent.IsLocked} +\label{enty-locked} +\textbf{ent.IsLocked(}\textit{entity}\textbf{ ent)} +\newline +Gibt \textbf{wahr} zurück falls eine entity Gesperrt/Verschlossen ist ansonsten wird \textbf{falsch} zurückgegeben. +\subsection{ent.GetParm} +\label{enty-getparm} +\textbf{ent.GetParm(}\textit{entity}\textbf{ ent, }\textit{integer}\textbf{ parm)} +\newline +Gibt den Wert für einen der luaParms der Entity \textbf{ent} als string zurück. Sollte der Wert nicht gesetzt sein wird \textbf{nil} zurückgegeben. +\newline +\textbf{parm} der gewünschte Wert (1 bis 4) +\subsection{ent.SetParm} +\label{enty-setparm} +\textbf{ent.SetParm(}\textit{entity }\textbf{ent, }\textit{integer }\textbf{parm, }\textit{string }\textbf{value)} +\newline +Setz einen gewünschten luaParm auf den Wert \textbf{value}. +\newline +\textbf{parm} gwünschter parm der gesetzt werden soll +\newline +\textbf{value} Wert auf den der parm gesetzt werden soll +\newpage +\section{mover} +\label{mver} +Wichtige Bemerkung: Sie sollten immer mover.Halt bzw. mover.HaltAngles aufrufen bevor Sie einen mover wieder durch Aufruf einer entsprechenden Funktion bewegen. Ansonsten wird die Bewegung der Entity nicht korrekt funktionieren. +\subsection{mover.Halt} +\label{mver-halt} +\textbf{mover.Halt(}\textit{entity}\textbf{ ent)} +\newline +Stoppt sofort jegliche Bewegung (ausgenommen Drehungen). +\subsection{mover.HaltAngles} +\label{mver-haltangles} +\textbf{mover.HaltAngles(}\textit{entity}\textbf{ ent)} +\newline +Stoppt sofort jegliche Drehung. +\subsection{mover.AsTrain} +\label{mver-astrain} +\textbf{mover.AsTrain(}\textit{entity}\textbf{ mover, }\textit{entity}\textbf{ target, }\textit{float}\textbf{ speed)} +\newline +Bewegt die Entity wie ein\textit{func\_train}. Ziel muss ein \textit{path\_corner} sein. +\newline +\textbf{target} die erste \textit{path\_corner} +\subsection{mover.SetAngles} +\label{mver-setangles} +\textbf{mover.SetAngles(}\textit{entity}\textbf{ ent, }\textit{vector}\textbf{ angles)} or \textbf{mover.SetAngles(}\textit{entity}\textbf{ ent, }\textit{float}\textbf{ x, }\textit{float}\textbf{ y, }\textit{float}\textbf{ z)} +\newline +Setzt die angles von \textbf{ent} zu dem angegebenen Wert(en). +\subsection{mover.SetPosition} +\label{mver-setposition} +\textbf{mover.SetPosition(}\textit{entity}\textbf{ ent, }\textit{vector}\textbf{ pos)} or \textbf{mover.SetPosition(}\textit{entity}\textbf{ ent, }\textit{float}\textbf{ x, }\textit{float}\textbf{ y, }\textit{float}\textbf{ z)} +\newline +Set die Origin von \textbf{ent} zu den angegebenen Wert(en) und bewegt sie sofort dort hin. +\subsection{mover.ToAngles} +\label{mver-toangles} +\textbf{mover.ToAngles}(\textit{entity}\textbf{ ent, }\textit{float}\textbf{ speed, }\textit{vector}\textbf{ angles)} or \textbf{mover.ToAngles(}\textit{entity}\textbf{ ent, }\textit{float}\textbf{ speed, }\textit{float}\textbf{ x, }\textit{float}\textbf{ y, }\textit{float}\textbf{ z)} +\newline +Rotiert \textbf{ent} zu den angegebenen angles. +\subsection{mover.ToPosition} +\label{mver-toposition} +\textbf{mover.ToPosition(}\textit{entity}\textbf{ ent, }\textit{float}\textbf{ speed, }\textit{vector}\textbf{ angles)} or +\newline +\textbf{mover.ToPosition(}\textit{entity}\textbf{ ent, }\textit{float}\textbf{ speed, }\textit{float}\textbf{ x, }\textit{float}\textbf{ y, }\textit{float}\textbf{ z)} +\newline +Bewegt \textbf{ent} zu angegebenen Position. +\newpage +\section{sound} +\label{sound} +Diese Bibliothek fügt die Möglichkeit hinzu Sounds abzuspielen sowie zu Verwalten. +\subsection{Sound Kanäle} +\label{sound-chan} +Einige Funktionen dieser Bibliothek haben einen Audiokanal als Parameter. Im Normalfall wird es reichen den Kanal CHAN\_AUTO zu benutzen der die Engine die Auswahl treffen lässt. Dennoch besteht die Möglichkeit den Kanal selbst zu wählen. +\newline +Hier ist eine Tabelle mit den verschiedenen Kanälen und ihrer Werte für den Funktionsparameter: +\newline +\begin{center} +\begin{tabular}[c]{| c | c |} +\hline +CHAN\_AUTO & 0 \\ +\hline +CHAN\_LOCAL & 1 \\ +\hline +CHAN\_WEAPON & 2 \\ +\hline +CHAN\_VOICE & 3 \\ +\hline +CHAN\_ITEM & 4 \\ +\hline +CHAN\_BODY & 5 \\ +\hline +CHAN\_LOCAL\_SOUND & 6 \\ +\hline +CHAN\_ANNOUNCER & 7 \\ +\hline +CHAN\_MENU1 & 8 \\ +\hline +\end{tabular} +\end{center} +\subsection{sound.PlaySound} +\label{snd-playsnd} +\textbf{sound.PlaySound(}\textit{entity}\textbf{ ent, }\textit{integer}\textbf{ chan, }\textit{string}\textbf{ sound)} +\newline +Versucht eine Audiodatei \textbf{sound} auf dem Kanal \textbf{chan} auf der Entity \textbf{ent} abzuspielen. +\newpage +\chapter{Beispiele} +\label{examples} +Dieses Kapitel der Dokumentation enthält Beispiele welche dazu hilfreich sein könnten einige Funktionen besser zu verstehen. +\section{Beispiel 1 - Hallo Welt} +\label{example1} +Das ist ein Beispiel das einfach bei jeder Programmiersprache Pflicht ist. +\subsection{Hallo Welt für game} +\label{example11} +\lstinputlisting[frame=single, label=helloworldgame, caption=Hallo Welt für game]{examples/HelloWorldGame.lua} +Wie Sie vielleicht erkennen ist die eine Funktion für luaUse (Man kann das am Funktionskopf sehen). +\subsection{Hallo Welt für einen Spieler} +\label{example12} +\lstinputlisting[frame=single, label=helloworldclient, caption=Hallo Welt für Spieler]{examples/HelloWorldClient.lua} +Wie Sie vielleicht erkennen ist die eine Funktion für luaUse (Man kann das am Funktionskopf sehen). +\lstinputlisting[frame=single, label=helloworldclient1, caption=First function]{examples/HelloWorldClient1.lua} +Diese Funktion gibt eine Nachricht in die Spielerkonsole aus. +\newline +\lstinline|activator:GetNumber()| holt die Entitynummer zurück welches in diesem Fall auch die Clientnummer ist. +\newline +\lstinline|activator:GetClientname()| holt den Namen des Spielers +\lstinputlisting[frame=single, label=helloworldclient2, caption=Second function]{examples/HelloWorldClient2.lua} +Diese Funktion gibt eine Nachricht auf der Mitte des Bildschirm eines Spielers aus. +\lstinputlisting[frame=single, label=helloworldclient3, caption=Third function]{examples/HelloWorldClient3.lua} +Diese Funktion gibt eine Nachricht in der unteren rechten Ecke des Bildschirmes eines Spielers aus. +\subsection{Hallo Welt für alle Spieler} +\label{example13} +\lstinputlisting[frame=single, label=helloworldclientall, caption=Hallo Welt für alle Spieler]{examples/HelloWorldClientAll.lua} +Dieses Beispielt ähnelt dem vorherigen sehr stark, der einzige Unterschied liegt darin das anstatt einer Clientnummer die -1 das erste argument ist. Dies führt dazu das die Nachricht für alle Spieler ausgegeben wird. +\section{Beispiel 2 - Entities Finden} +\label{example2} +Diese Beispiele werden die verschiedenen Wege zeigen eine Entity zu finden. +\subsection{Entities über ihren targetname finden} +\label{example21} +\lstinputlisting[frame=single, label=findents1, caption=Entity über ihren targername finden]{examples/FindingEnts1.lua} +Zu beachten ist das \lstinline|entity.Find()| immer nur die erste Entity zurück gibt die gefunden wird. Das bedeutet das wenn es mehrere Entities mit dem selben targetname gibt es passieren kann das Sie die Entity die Sie suchen auf diese weiße nicht finden können. +\newline +Neben dem Suchen nach einer Entity zeigt dieses Beispiel auch wie man lokale Variablen benutzt. +\subsection{Entities über ihre Entitynummer finden} +\label{example22} +\lstinputlisting[frame=single, label=findents2, caption=Entities über ihre Entitynummer finden]{examples/FindingEnts2.lua} +Dies ist ein fast absolut sicherer Weg eine Entity zu finden, man muss aber folgendes beachten: Die Entitynummer für eine Entity ist unterschiedlich je nachdem ob man eine map lokal oder auf einem Server lädt. +\subsection{Entities über ihr Brush Modell finden} +\label{example23} +\lstinputlisting[frame=single, label=findents3, caption=Entities über ihr Brush Modell finden]{examples/FindingEnts3.lua} +Absolut eindeutiger Weg eine Entity zu finden, funktioniert allerdings nur für Brushentities. +\section{Beispiel 3 - Entities Spawnen} +Dieses Beispielt zeigt wie man Entities über das Scripting spawnen kann. Es is möglich fast alle Entities zu spawnen die kein Brush Modell haben. Darüber hinaus kann man einige Entities spawnen die Brush Modells benötigen sofern diese nicht sichtbar sind (zum Beispiel trigger\_* Entities). +\lstinputlisting[frame=single, label=spawnents, caption=Eine Entity Spawnen]{examples/SpawningEnts.lua} +\newpage +Also was wird getan und warum? +\newline +\begin{lstlisting} +local ent = entity.Spawn() +\end{lstlisting} +Versucht eine neue Entity zu spawnen und sie \lstinline|ent| zuzuweisen. +\newline +\begin{lstlisting} +if ent == nil then return +\end{lstlisting} +Dies überprüft ob das Spawnen der Entity erfolgreich war. Sollte dies nicht der Fall sein wird die weitere Abarbeitung der Funktion verhindert. +\newline +\begin{lstlisting} +ent:SetKeyValue("classname", "info_notnull"); +\end{lstlisting} +Setzt den Klassennamen der Entity und macht sie damit zu einer Entity eines bestimmten Typs. +\newline +\begin{lstlisting} +mover:SetPosition(0, 0, 0); +\end{lstlisting} +Setzt die Origin der Entity. +\newline +\begin{lstlisting} +entity.CallSpawn(ent); +\end{lstlisting} +Sorgt für den Aufruf der Spawnfunktion der Entity in der Spiellogik. +\chapter{Wie man ...} +\label{howto} +\section{Turbolifte zu älteren RPG-X Maps hinzufügt} +\label{howto-x2turbo} +Kommt demnächst ... +\section{Transporter die das ui\_transporter benutzen zu älteren Maps hinzufügt} +\label{howto-uitrans} +Kommt demnächst ... +\section{func\_usable zu func\_forcefield konvertiert} +\label{howto-usabletoforcefield} +Hier wird gezeigt wie man eine func\_usable in ein func\_forcefield konvertiert. Bevor wir beginnen müssen wir aber erstmal einige Dinge herausfinden: +\begin{itemize} + \item Wie man eine func\_usable garantiert und fehlerfrei findet. + \item Wie die momentanen Spawnfalgs der Entity sind. +\end{itemize} +An diese informationen kommt man wie folgt: +\begin{itemize} + \item RPG-X2 starten und die Map laden. + \item Als Admin einloggen oder in die Adminklasse wechseln. + \item Zur func\_usable gehen und sicherstellen das die sichtbar ist. + \item Sie mit den Fadenkreuz anvisieren. + \item Konsole öffnen und folgendes Kommando eingeben: \textbf{getEntInfo}. +\end{itemize} +Man bekommt eine Liste mit nützlichen Informationen. Sollte die Entity einen targetname muss man überprüfen ob sie die einzige mit diesem Namen ist. Das tut man indem man \textbf{getEntByTargetname} gefolgt vom targetname eingibt. Falls nur eine Entity aufgelistet wird heißt dies das es nur diese eine Entity mit diesem targetname gibt. Damit sind wir fertig mit diesem Schritt. +\newline +Sollte es aber mehrere Entities mit diesem targetname geben verwendet man das Brush Modell der Entity um sie zu finden. Der Name des Brush Modells wurde bereits beim ausführen von \textbf{getEntInfo} angezeigt. +\newline +Der nächste Schritt ist es die Spawnflags dahingehen zu überprüfen ob sie dem entsprechen was man für das func\_forcefield braucht. Was zu tun ist für den Fall das die Spawnflags nicht passen werden wir unten sehen. +\newline +Nun fangen wir mit dem Scripting an. Der beste platz um eine Entitykonversion durchzuführen ist \textbf{InitGame}, da diese Funktion bereits bei jedem Mapanfang ausgeführt wird. +\lstinputlisting[frame=single, label=fustoff, caption=Beispiel 1]{examples/fusToff.lua} +\lstinputlisting[frame=single, label=fustoff2, caption=Beispiel 2]{examples/fusToff2.lua} +\end{document} diff --git a/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation Deu.toc b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation Deu.toc new file mode 100644 index 0000000..1a504f2 --- /dev/null +++ b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation Deu.toc @@ -0,0 +1,120 @@ +\select@language {ngerman} +\contentsline {chapter}{\numberline {1}Einf\IeC {\"u}hrung}{7} +\contentsline {section}{\numberline {1.1}Grundlegende Informationen}{7} +\contentsline {section}{\numberline {1.2}Vorvereinbarungen}{7} +\contentsline {chapter}{\numberline {2}Lua Hooks}{9} +\contentsline {section}{\numberline {2.1}Was ist ein Lua Hook}{9} +\contentsline {section}{\numberline {2.2}Statische Lua Hooks}{9} +\contentsline {subsection}{\numberline {2.2.1}InitGame}{9} +\contentsline {subsection}{\numberline {2.2.2}ShutdownGame}{9} +\contentsline {subsection}{\numberline {2.2.3}RunFrame}{10} +\contentsline {subsection}{\numberline {2.2.4}GClientPrint}{10} +\contentsline {subsection}{\numberline {2.2.5}GPrint}{10} +\contentsline {section}{\numberline {2.3}Dynamische Lua Hooks}{11} +\contentsline {subsection}{\numberline {2.3.1}luaThink}{11} +\contentsline {subsection}{\numberline {2.3.2}luaTouch}{11} +\contentsline {subsection}{\numberline {2.3.3}luaUse}{11} +\contentsline {subsection}{\numberline {2.3.4}luaHurt}{11} +\contentsline {subsection}{\numberline {2.3.5}luaDie}{11} +\contentsline {subsection}{\numberline {2.3.6}luaFree}{12} +\contentsline {subsection}{\numberline {2.3.7}luaReached}{12} +\contentsline {subsection}{\numberline {2.3.8}luaReachedAngular}{12} +\contentsline {subsection}{\numberline {2.3.9}luaTrigger}{12} +\contentsline {subsection}{\numberline {2.3.10}luaSpawn}{12} +\contentsline {chapter}{\numberline {3}RPG-X2 Map Scripting}{13} +\contentsline {section}{\numberline {3.1}Map scripts}{13} +\contentsline {section}{\numberline {3.2}Aufruf von Funktionen}{13} +\contentsline {chapter}{\numberline {4}RPG-X2 Lua Bibliotheken}{15} +\contentsline {section}{\numberline {4.1}game}{15} +\contentsline {subsection}{\numberline {4.1.1}game.Print}{15} +\contentsline {subsection}{\numberline {4.1.2}game.ClientPrint}{15} +\contentsline {subsection}{\numberline {4.1.3}game.CenterPrint}{15} +\contentsline {subsection}{\numberline {4.1.4}game.MessagePrint}{15} +\contentsline {subsection}{\numberline {4.1.5}game.LevelTime}{16} +\contentsline {subsection}{\numberline {4.1.6}game.SetGlobal}{16} +\contentsline {subsection}{\numberline {4.1.7}game.GetGlobal}{16} +\contentsline {section}{\numberline {4.2}qmath}{17} +\contentsline {subsection}{\numberline {4.2.1}qmath.abs}{17} +\contentsline {subsection}{\numberline {4.2.2}qmath.sin}{17} +\contentsline {subsection}{\numberline {4.2.3}qmath.cos}{17} +\contentsline {subsection}{\numberline {4.2.4}qmath.tan}{17} +\contentsline {subsection}{\numberline {4.2.5}qmath.asin}{17} +\contentsline {subsection}{\numberline {4.2.6}qmath.acos}{17} +\contentsline {subsection}{\numberline {4.2.7}qmath.atan}{17} +\contentsline {subsection}{\numberline {4.2.8}qmath.floor}{17} +\contentsline {subsection}{\numberline {4.2.9}qmath.ceil}{18} +\contentsline {subsection}{\numberline {4.2.10}qmath.fmod}{18} +\contentsline {subsection}{\numberline {4.2.11}qmath.modf}{18} +\contentsline {subsection}{\numberline {4.2.12}qmath.sqrt}{18} +\contentsline {subsection}{\numberline {4.2.13}qmath.log}{18} +\contentsline {subsection}{\numberline {4.2.14}qmath.log10}{18} +\contentsline {subsection}{\numberline {4.2.15}qmath.deg}{18} +\contentsline {subsection}{\numberline {4.2.16}qmath.rad}{18} +\contentsline {subsection}{\numberline {4.2.17}qmath.frexp}{18} +\contentsline {subsection}{\numberline {4.2.18}qmath.ldexp}{19} +\contentsline {subsection}{\numberline {4.2.19}qmath.min}{19} +\contentsline {subsection}{\numberline {4.2.20}qmath.max}{19} +\contentsline {subsection}{\numberline {4.2.21}qmath.random}{19} +\contentsline {subsection}{\numberline {4.2.22}qmath.crandom}{19} +\contentsline {section}{\numberline {4.3}vector}{20} +\contentsline {subsection}{\numberline {4.3.1}vector.New}{20} +\contentsline {subsection}{\numberline {4.3.2}vector.Construct}{20} +\contentsline {subsection}{\numberline {4.3.3}vector.Set}{20} +\contentsline {subsection}{\numberline {4.3.4}vector.clear}{20} +\contentsline {subsection}{\numberline {4.3.5}vector.Add}{20} +\contentsline {subsection}{\numberline {4.3.6}vector.Substract}{20} +\contentsline {subsection}{\numberline {4.3.7}vector.Scale}{20} +\contentsline {subsection}{\numberline {4.3.8}vector.Length}{21} +\contentsline {subsection}{\numberline {4.3.9}vector.Normalize}{21} +\contentsline {subsection}{\numberline {4.3.10}vector.RotateAroundPoint}{21} +\contentsline {subsection}{\numberline {4.3.11}vector.Perpendicular}{21} +\contentsline {section}{\numberline {4.4}entity}{22} +\contentsline {subsection}{\numberline {4.4.1}entity.Find}{22} +\contentsline {subsection}{\numberline {4.4.2}entity.FindNumber}{22} +\contentsline {subsection}{\numberline {4.4.3}entity.FindBModel}{22} +\contentsline {subsection}{\numberline {4.4.4}ent.GetNumber}{22} +\contentsline {subsection}{\numberline {4.4.5}ent.SetKeyValue}{22} +\contentsline {subsection}{\numberline {4.4.6}entity.Remove}{22} +\contentsline {subsection}{\numberline {4.4.7}ent.GetOrigin}{23} +\contentsline {subsection}{\numberline {4.4.8}ent.IsClient}{23} +\contentsline {subsection}{\numberline {4.4.9}ent.GetClientname}{23} +\contentsline {subsection}{\numberline {4.4.10}ent.GetClassname}{23} +\contentsline {subsection}{\numberline {4.4.11}ent.SetClassname}{23} +\contentsline {subsection}{\numberline {4.4.12}ent.GetTargetname}{23} +\contentsline {subsection}{\numberline {4.4.13}ent.SetupTrigger}{23} +\contentsline {subsection}{\numberline {4.4.14}entity.GetTarget}{23} +\contentsline {subsection}{\numberline {4.4.15}entity.Use}{23} +\contentsline {subsection}{\numberline {4.4.16}entity.Spawn}{24} +\contentsline {subsection}{\numberline {4.4.17}entiy.CallSpawn}{24} +\contentsline {subsection}{\numberline {4.4.18}entity.DelayedCallSpawn}{24} +\contentsline {subsection}{\numberline {4.4.19}entity.RemoveSpawns}{24} +\contentsline {subsection}{\numberline {4.4.20}ent.Lock}{24} +\contentsline {subsection}{\numberline {4.4.21}ent.Unlock}{24} +\contentsline {subsection}{\numberline {4.4.22}ent.IsLocked}{24} +\contentsline {subsection}{\numberline {4.4.23}ent.GetParm}{24} +\contentsline {subsection}{\numberline {4.4.24}ent.SetParm}{25} +\contentsline {section}{\numberline {4.5}mover}{26} +\contentsline {subsection}{\numberline {4.5.1}mover.Halt}{26} +\contentsline {subsection}{\numberline {4.5.2}mover.HaltAngles}{26} +\contentsline {subsection}{\numberline {4.5.3}mover.AsTrain}{26} +\contentsline {subsection}{\numberline {4.5.4}mover.SetAngles}{26} +\contentsline {subsection}{\numberline {4.5.5}mover.SetPosition}{26} +\contentsline {subsection}{\numberline {4.5.6}mover.ToAngles}{26} +\contentsline {subsection}{\numberline {4.5.7}mover.ToPosition}{27} +\contentsline {section}{\numberline {4.6}sound}{28} +\contentsline {subsection}{\numberline {4.6.1}Sound Kan\IeC {\"a}le}{28} +\contentsline {subsection}{\numberline {4.6.2}sound.PlaySound}{28} +\contentsline {chapter}{\numberline {5}Beispiele}{29} +\contentsline {section}{\numberline {5.1}Beispiel 1 - Hallo Welt}{29} +\contentsline {subsection}{\numberline {5.1.1}Hallo Welt f\IeC {\"u}r game}{29} +\contentsline {subsection}{\numberline {5.1.2}Hallo Welt f\IeC {\"u}r einen Spieler}{29} +\contentsline {subsection}{\numberline {5.1.3}Hallo Welt f\IeC {\"u}r alle Spieler}{30} +\contentsline {section}{\numberline {5.2}Beispiel 2 - Entities Finden}{30} +\contentsline {subsection}{\numberline {5.2.1}Entities \IeC {\"u}ber ihren targetname finden}{31} +\contentsline {subsection}{\numberline {5.2.2}Entities \IeC {\"u}ber ihre Entitynummer finden}{31} +\contentsline {subsection}{\numberline {5.2.3}Entities \IeC {\"u}ber ihr Brush Modell finden}{31} +\contentsline {section}{\numberline {5.3}Beispiel 3 - Entities Spawnen}{31} +\contentsline {chapter}{\numberline {6}Wie man ...}{35} +\contentsline {section}{\numberline {6.1}Turbolifte zu \IeC {\"a}lteren RPG-X Maps hinzuf\IeC {\"u}gt}{35} +\contentsline {section}{\numberline {6.2}Transporter die das ui\_transporter benutzen zu \IeC {\"a}lteren Maps hinzuf\IeC {\"u}gt}{35} +\contentsline {section}{\numberline {6.3}func\_usable zu func\_forcefield konvertiert}{35} diff --git a/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation WIP.pdf b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation WIP.pdf new file mode 100644 index 0000000..4c1c4a9 Binary files /dev/null and b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation WIP.pdf differ diff --git a/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.aux b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.aux new file mode 100644 index 0000000..3468f93 --- /dev/null +++ b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.aux @@ -0,0 +1,280 @@ +\relax +\@writefile{toc}{\contentsline {chapter}{\numberline {1}Introduction}{5}} +\@writefile{lof}{\addvspace {10\p@ }} +\@writefile{lot}{\addvspace {10\p@ }} +\@writefile{lol}{\addvspace {10\p@ }} +\newlabel{intro}{{1}{5}} +\@writefile{toc}{\contentsline {section}{\numberline {1.1}General Information}{5}} +\newlabel{gen-info}{{1.1}{5}} +\@writefile{toc}{\contentsline {section}{\numberline {1.2}Prerequisites}{5}} +\newlabel{preq}{{1.2}{5}} +\@writefile{toc}{\contentsline {chapter}{\numberline {2}Lua Hooks}{6}} +\@writefile{lof}{\addvspace {10\p@ }} +\@writefile{lot}{\addvspace {10\p@ }} +\@writefile{lol}{\addvspace {10\p@ }} +\newlabel{lua-hooks}{{2}{6}} +\@writefile{toc}{\contentsline {section}{\numberline {2.1}What is a Lua Hook}{6}} +\newlabel{wia-lh}{{2.1}{6}} +\@writefile{toc}{\contentsline {section}{\numberline {2.2}Static Lua Hooks}{6}} +\newlabel{s-lh}{{2.2}{6}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.2.1}InitGame}{6}} +\newlabel{init-game}{{2.2.1}{6}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.2.2}ShutdownGame}{6}} +\newlabel{shutdown-game}{{2.2.2}{6}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.2.3}RunFrame}{6}} +\newlabel{run-frame}{{2.2.3}{6}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.2.4}GClientPrint}{6}} +\newlabel{cli-print}{{2.2.4}{6}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.2.5}GPrint}{7}} +\newlabel{g-print}{{2.2.5}{7}} +\@writefile{toc}{\contentsline {section}{\numberline {2.3}Dynamic Lua Hooks}{8}} +\newlabel{dyn-lh}{{2.3}{8}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3.1}luaThink}{8}} +\newlabel{luaThink}{{2.3.1}{8}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3.2}luaTouch}{8}} +\newlabel{luaTouch}{{2.3.2}{8}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3.3}luaUse}{8}} +\newlabel{luaUse}{{2.3.3}{8}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3.4}luaHurt}{8}} +\newlabel{luaHurt}{{2.3.4}{8}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3.5}luaDie}{8}} +\newlabel{luaDie}{{2.3.5}{8}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3.6}luaFree}{9}} +\newlabel{luaFree}{{2.3.6}{9}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3.7}luaReached}{9}} +\newlabel{luaReached}{{2.3.7}{9}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3.8}luaReachedAngular}{9}} +\newlabel{luaReachedAngular}{{2.3.8}{9}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3.9}luaTrigger}{9}} +\newlabel{luaTrigger}{{2.3.9}{9}} +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3.10}luaSpawn}{9}} +\newlabel{luaSpawn}{{2.3.10}{9}} +\@writefile{toc}{\contentsline {chapter}{\numberline {3}RPG-X2 Map Scripting}{10}} +\@writefile{lof}{\addvspace {10\p@ }} +\@writefile{lot}{\addvspace {10\p@ }} +\@writefile{lol}{\addvspace {10\p@ }} +\newlabel{rpgx2-mapscripting}{{3}{10}} +\@writefile{toc}{\contentsline {section}{\numberline {3.1}Map scripts}{10}} +\newlabel{map-scripts}{{3.1}{10}} +\@writefile{toc}{\contentsline {section}{\numberline {3.2}Calling Functions}{10}} +\newlabel{map-callingfunction}{{3.2}{10}} +\@writefile{toc}{\contentsline {chapter}{\numberline {4}RPG-X2 Lua Libraries}{11}} +\@writefile{lof}{\addvspace {10\p@ }} +\@writefile{lot}{\addvspace {10\p@ }} +\@writefile{lol}{\addvspace {10\p@ }} +\newlabel{rpgx2-llibs}{{4}{11}} +\@writefile{toc}{\contentsline {section}{\numberline {4.1}game}{11}} +\newlabel{g}{{4.1}{11}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.1.1}game.Print}{11}} +\newlabel{g-prnt}{{4.1.1}{11}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.1.2}game.ClientPrint}{11}} +\newlabel{g-clientprint}{{4.1.2}{11}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.1.3}game.CenterPrint}{11}} +\newlabel{g-centerprint}{{4.1.3}{11}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.1.4}game.MessagePrint}{11}} +\newlabel{g-messagepritn}{{4.1.4}{11}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.1.5}game.LevelTime}{11}} +\newlabel{g-leveltime}{{4.1.5}{11}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.1.6}game.SetGlobal}{11}} +\newlabel{g-setglobal}{{4.1.6}{11}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.1.7}game.GetGlobal}{12}} +\newlabel{g-getglobal}{{4.1.7}{12}} +\@writefile{toc}{\contentsline {section}{\numberline {4.2}qmath}{13}} +\newlabel{qmath}{{4.2}{13}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.1}qmath.abs}{13}} +\newlabel{qm-abs}{{4.2.1}{13}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.2}qmath.sin}{13}} +\newlabel{qm-sin}{{4.2.2}{13}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.3}qmath.cos}{13}} +\newlabel{qm-cos}{{4.2.3}{13}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.4}qmath.tan}{13}} +\newlabel{qm-tan}{{4.2.4}{13}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.5}qmath.asin}{13}} +\newlabel{qm-asin}{{4.2.5}{13}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.6}qmath.acos}{13}} +\newlabel{qm-acos}{{4.2.6}{13}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.7}qmath.atan}{13}} +\newlabel{qm-atan}{{4.2.7}{13}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.8}qmath.floor}{13}} +\newlabel{qm-floor}{{4.2.8}{13}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.9}qmath.ceil}{14}} +\newlabel{qm-ceil}{{4.2.9}{14}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.10}qmath.fmod}{14}} +\newlabel{qm-fmod}{{4.2.10}{14}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.11}qmath.modf}{14}} +\newlabel{qm-modf}{{4.2.11}{14}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.12}qmath.sqrt}{14}} +\newlabel{qm-sqrt}{{4.2.12}{14}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.13}qmath.log}{14}} +\newlabel{qm-log}{{4.2.13}{14}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.14}qmath.log10}{14}} +\newlabel{qm-log10}{{4.2.14}{14}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.15}qmath.deg}{14}} +\newlabel{qm-deg}{{4.2.15}{14}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.16}qmath.rad}{14}} +\newlabel{qm-rad}{{4.2.16}{14}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.17}qmath.frexp}{14}} +\newlabel{qm-frexp}{{4.2.17}{14}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.18}qmath.ldexp}{15}} +\newlabel{qm-ldexp}{{4.2.18}{15}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.19}qmath.min}{15}} +\newlabel{qm-min}{{4.2.19}{15}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.20}qmath.max}{15}} +\newlabel{qm-max}{{4.2.20}{15}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.21}qmath.random}{15}} +\newlabel{qm-random}{{4.2.21}{15}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.2.22}qmath.crandom}{15}} +\newlabel{qm-crandom}{{4.2.22}{15}} +\@writefile{toc}{\contentsline {section}{\numberline {4.3}vector}{16}} +\newlabel{vect}{{4.3}{16}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3.1}vector.New}{16}} +\newlabel{vect-new}{{4.3.1}{16}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3.2}vector.Construct}{16}} +\newlabel{vect-cons}{{4.3.2}{16}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3.3}vector.Set}{16}} +\newlabel{vect-set}{{4.3.3}{16}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3.4}vector.clear}{16}} +\newlabel{vect-clear}{{4.3.4}{16}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3.5}vector.Add}{16}} +\newlabel{vect-add}{{4.3.5}{16}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3.6}vector.Substract}{16}} +\newlabel{vect-sub}{{4.3.6}{16}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3.7}vector.Scale}{16}} +\newlabel{vect-scale}{{4.3.7}{16}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3.8}vector.Length}{17}} +\newlabel{vect-length}{{4.3.8}{17}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3.9}vector.Normalize}{17}} +\newlabel{vect-norm}{{4.3.9}{17}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3.10}vector.RotateAroundPoint}{17}} +\newlabel{vect-rotarndpnt}{{4.3.10}{17}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.3.11}vector.Perpendicular}{17}} +\newlabel{vect-Perpendicular}{{4.3.11}{17}} +\@writefile{toc}{\contentsline {section}{\numberline {4.4}entity}{18}} +\newlabel{enty}{{4.4}{18}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.1}entity.Find}{18}} +\newlabel{enty-find}{{4.4.1}{18}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.2}entity.FindNumber}{18}} +\newlabel{enty.findnumber}{{4.4.2}{18}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.3}entity.FindBModel}{18}} +\newlabel{enty-findbmodel}{{4.4.3}{18}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.4}ent.GetNumber}{18}} +\newlabel{enty-getnumber}{{4.4.4}{18}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.5}ent.SetKeyValue}{18}} +\newlabel{enty-setkeyvalue}{{4.4.5}{18}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.6}entity.Remove}{18}} +\newlabel{enty-remove}{{4.4.6}{18}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.7}ent.GetOrigin}{18}} +\newlabel{enty-getorigin}{{4.4.7}{18}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.8}ent.IsClient}{19}} +\newlabel{enty-isclient}{{4.4.8}{19}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.9}ent.GetClientname}{19}} +\newlabel{enty-getclientname}{{4.4.9}{19}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.10}ent.GetClassname}{19}} +\newlabel{enty-getclassname}{{4.4.10}{19}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.11}ent.SetClassname}{19}} +\newlabel{enty-setclassname}{{4.4.11}{19}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.12}ent.GetTargetname}{19}} +\newlabel{enty-gettargetname}{{4.4.12}{19}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.13}ent.SetupTrigger}{19}} +\newlabel{enty-setuptrigger}{{4.4.13}{19}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.14}entity.GetTarget}{19}} +\newlabel{enty-gettarget}{{4.4.14}{19}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.15}entity.Use}{19}} +\newlabel{enty-use}{{4.4.15}{19}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.16}entity.Spawn}{19}} +\newlabel{enty-spawn}{{4.4.16}{19}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.17}entiy.CallSpawn}{20}} +\newlabel{enty-callspawn}{{4.4.17}{20}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.18}entity.DelayedCallSpawn}{20}} +\newlabel{enty-delayedcallspawn}{{4.4.18}{20}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.19}entity.RemoveSpawns}{20}} +\newlabel{enty-removespawns}{{4.4.19}{20}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.20}ent.Lock}{20}} +\newlabel{enty-lock}{{4.4.20}{20}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.21}ent.Unlock}{20}} +\newlabel{enty-unlock}{{4.4.21}{20}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.22}ent.IsLocked}{20}} +\newlabel{enty-locked}{{4.4.22}{20}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.23}ent.GetParm}{20}} +\newlabel{enty-getparm}{{4.4.23}{20}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.4.24}ent.SetParm}{20}} +\newlabel{enty-setparm}{{4.4.24}{20}} +\@writefile{toc}{\contentsline {section}{\numberline {4.5}mover}{21}} +\newlabel{mver}{{4.5}{21}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.5.1}mover.Halt}{21}} +\newlabel{mver-halt}{{4.5.1}{21}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.5.2}mover.HaltAngles}{21}} +\newlabel{mver-haltangles}{{4.5.2}{21}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.5.3}mover.AsTrain}{21}} +\newlabel{mver-astrain}{{4.5.3}{21}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.5.4}mover.SetAngles}{21}} +\newlabel{mver-setangles}{{4.5.4}{21}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.5.5}mover.SetPosition}{21}} +\newlabel{mver-setposition}{{4.5.5}{21}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.5.6}mover.ToAngles}{21}} +\newlabel{mver-toangles}{{4.5.6}{21}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.5.7}mover.ToPosition}{21}} +\newlabel{mver-toposition}{{4.5.7}{21}} +\@writefile{toc}{\contentsline {section}{\numberline {4.6}sound}{22}} +\newlabel{sound}{{4.6}{22}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.6.1}Sound Channels}{22}} +\newlabel{sound-chan}{{4.6.1}{22}} +\@writefile{toc}{\contentsline {subsection}{\numberline {4.6.2}sound.PlaySound}{22}} +\newlabel{snd-playsnd}{{4.6.2}{22}} +\@writefile{toc}{\contentsline {chapter}{\numberline {5}Examples}{23}} +\@writefile{lof}{\addvspace {10\p@ }} +\@writefile{lot}{\addvspace {10\p@ }} +\@writefile{lol}{\addvspace {10\p@ }} +\newlabel{examples}{{5}{23}} +\@writefile{toc}{\contentsline {section}{\numberline {5.1}Example 1 - HelloWorld}{23}} +\newlabel{example1}{{5.1}{23}} +\@writefile{toc}{\contentsline {subsection}{\numberline {5.1.1}Hello World for game}{23}} +\newlabel{example11}{{5.1.1}{23}} +\newlabel{helloworldgame}{{5.1}{23}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {5.1}Hello World for game}{23}} +\@writefile{toc}{\contentsline {subsection}{\numberline {5.1.2}Hello World for a client}{23}} +\newlabel{example12}{{5.1.2}{23}} +\newlabel{helloworldclient}{{5.2}{23}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {5.2}Hello World for client}{23}} +\newlabel{helloworldclient1}{{5.3}{23}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {5.3}First function}{23}} +\newlabel{helloworldclient2}{{5.4}{24}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {5.4}Second function}{24}} +\newlabel{helloworldclient3}{{5.5}{24}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {5.5}Third function}{24}} +\@writefile{toc}{\contentsline {subsection}{\numberline {5.1.3}Hello World for all clients}{24}} +\newlabel{example13}{{5.1.3}{24}} +\newlabel{helloworldclientall}{{5.6}{24}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {5.6}Hello World for all client}{24}} +\@writefile{toc}{\contentsline {section}{\numberline {5.2}Example 2 - Finding Entities}{24}} +\newlabel{example2}{{5.2}{24}} +\@writefile{toc}{\contentsline {subsection}{\numberline {5.2.1}Finding entities by their targetnames}{24}} +\newlabel{example21}{{5.2.1}{24}} +\newlabel{findents1}{{5.7}{24}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {5.7}Find an entity by its targetname}{24}} +\@writefile{toc}{\contentsline {subsection}{\numberline {5.2.2}Finding entities by their entity number}{25}} +\newlabel{example22}{{5.2.2}{25}} +\newlabel{findents2}{{5.8}{25}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {5.8}Find an entity by its entity number}{25}} +\@writefile{toc}{\contentsline {subsection}{\numberline {5.2.3}Finding entities by thier brush model}{25}} +\newlabel{example23}{{5.2.3}{25}} +\newlabel{findents3}{{5.9}{25}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {5.9}Find an entity by its brush model}{25}} +\@writefile{toc}{\contentsline {section}{\numberline {5.3}Example 3 - Spawning entities}{25}} +\newlabel{spawnents}{{5.10}{25}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {5.10}Spawning an entity}{25}} +\@writefile{toc}{\contentsline {chapter}{\numberline {6}How to ...}{27}} +\@writefile{lof}{\addvspace {10\p@ }} +\@writefile{lot}{\addvspace {10\p@ }} +\@writefile{lol}{\addvspace {10\p@ }} +\newlabel{howto}{{6}{27}} +\@writefile{toc}{\contentsline {section}{\numberline {6.1}add RPG-X2 Turbolifts to older maps}{27}} +\newlabel{howto-x2turbo}{{6.1}{27}} +\@writefile{toc}{\contentsline {section}{\numberline {6.2}add Transporters with ui\_transporter to older maps}{27}} +\newlabel{howto-uitrans}{{6.2}{27}} +\@writefile{toc}{\contentsline {section}{\numberline {6.3}convert func\_usable force field from older maps to func\_forcefield}{27}} +\newlabel{howto-usabletoforcefield}{{6.3}{27}} +\newlabel{fustoff}{{6.1}{28}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {6.1}Example 1}{28}} +\newlabel{fustoff2}{{6.2}{28}} +\@writefile{lol}{\contentsline {lstlisting}{\numberline {6.2}Example 2}{28}} diff --git a/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.dvi b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.dvi new file mode 100644 index 0000000..6fbdb89 Binary files /dev/null and b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.dvi differ diff --git a/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.log b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.log new file mode 100644 index 0000000..8dfd961 --- /dev/null +++ b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.log @@ -0,0 +1,312 @@ +This is pdfTeX, Version 3.1415926-1.40.11 (MiKTeX 2.9) (preloaded format=pdflatex 2010.12.30) 30 DEC 2010 13:17 +entering extended mode +**C:/stvoy/Code-DM/RPG-X2*Lua*Documentation/RPG-X2*Lua*Documentation.tex +("C:/stvoy/Code-DM/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.tex" +LaTeX2e <2009/09/24> +Babel and hyphenation patterns for english, afrikaans, ancientgreek, ar +abic, armenian, assamese, basque, bengali, bokmal, bulgarian, catalan, coptic, +croatian, czech, danish, dutch, esperanto, estonian, farsi, finnish, french, ga +lician, german, german-x-2009-06-19, greek, gujarati, hindi, hungarian, iceland +ic, indonesian, interlingua, irish, italian, kannada, kurmanji, lao, latin, lat +vian, lithuanian, malayalam, marathi, mongolian, mongolianlmc, monogreek, ngerm +an, ngerman-x-2009-06-19, nynorsk, oriya, panjabi, pinyin, polish, portuguese, +romanian, russian, sanskrit, serbian, slovak, slovenian, spanish, swedish, swis +sgerman, tamil, telugu, turkish, turkmen, ukenglish, ukrainian, uppersorbian, u +senglishmax, welsh, loaded. +("C:\Program Files\MiKTeX 2.9\tex\latex\koma-script\scrreprt.cls" +Document Class: scrreprt 2010/09/17 v3.07 KOMA-Script document class (report) +("C:\Program Files\MiKTeX 2.9\tex\latex\koma-script\scrkbase.sty" +Package: scrkbase 2010/09/17 v3.07 KOMA-Script package (KOMA-Script-dependent b +asics and keyval usage) + +("C:\Program Files\MiKTeX 2.9\tex\latex\koma-script\scrbase.sty" +Package: scrbase 2010/09/17 v3.07 KOMA-Script package (KOMA-Script-independent +basics and keyval usage) + +("C:\Program Files\MiKTeX 2.9\tex\latex\graphics\keyval.sty" +Package: keyval 1999/03/16 v1.13 key=value parser (DPC) +\KV@toks@=\toks14 +) +("C:\Program Files\MiKTeX 2.9\tex\latex\koma-script\scrlfile.sty" +Package: scrlfile 2009/03/25 v3.03 KOMA-Script package (loading files) + +Package scrlfile, 2009/03/25 v3.03 KOMA-Script package (loading files) + Copyright (C) Markus Kohm + +))) ("C:\Program Files\MiKTeX 2.9\tex\latex\koma-script\tocbasic.sty" +Package: tocbasic 2010/09/14 v3.06a KOMA-Script package (handling toc-files) +) +Package tocbasic Info: omitting babel extension for `toc' +(tocbasic) because of feature `nobabel' available +(tocbasic) for `toc' on input line 117. +Package tocbasic Info: omitting babel extension for `lof' +(tocbasic) because of feature `nobabel' available +(tocbasic) for `lof' on input line 118. +Package tocbasic Info: omitting babel extension for `lot' +(tocbasic) because of feature `nobabel' available +(tocbasic) for `lot' on input line 119. +Class scrreprt Info: File `scrsize11pt.clo' used instead of +(scrreprt) file `scrsize11.clo' to setup font sizes on input line 131 +0. + +("C:\Program Files\MiKTeX 2.9\tex\latex\koma-script\scrsize11pt.clo" +File: scrsize11pt.clo 2010/09/17 v3.07 KOMA-Script font size class option (11pt +) +) +("C:\Program Files\MiKTeX 2.9\tex\latex\koma-script\typearea.sty" +Package: typearea 2010/09/17 v3.07 KOMA-Script package (type area) + +Package typearea, 2010/09/17 v3.07 KOMA-Script package (type area) + Copyright (C) Frank Neukam, 1992-1994 + Copyright (C) Markus Kohm, 1994- + +\ta@bcor=\skip41 +\ta@div=\count79 +\ta@hblk=\skip42 +\ta@vblk=\skip43 +\ta@temp=\skip44 +Package typearea Info: These are the values describing the layout: +(typearea) DIV = 10 +(typearea) BCOR = 0.0pt +(typearea) \paperwidth = 597.50793pt +(typearea) \textwidth = 418.25555pt +(typearea) DIV departure = -6% +(typearea) \evensidemargin = 17.3562pt +(typearea) \oddsidemargin = 17.3562pt +(typearea) \paperheight = 845.04694pt +(typearea) \textheight = 595.80026pt +(typearea) \topmargin = -25.16531pt +(typearea) \headheight = 17.0pt +(typearea) \headsep = 20.40001pt +(typearea) \topskip = 11.0pt +(typearea) \footskip = 47.60002pt +(typearea) \baselineskip = 13.6pt +(typearea) on input line 1134. +) +\c@part=\count80 +\c@chapter=\count81 +\c@section=\count82 +\c@subsection=\count83 +\c@subsubsection=\count84 +\c@paragraph=\count85 +\c@subparagraph=\count86 +\abovecaptionskip=\skip45 +\belowcaptionskip=\skip46 +\c@pti@nb@sid@b@x=\box26 +\c@figure=\count87 +\c@table=\count88 +\bibindent=\dimen102 +) ("C:\Program Files\MiKTeX 2.9\tex\latex\base\inputenc.sty" +Package: inputenc 2008/03/30 v1.1d Input encoding file +\inpenc@prehook=\toks15 +\inpenc@posthook=\toks16 + +("C:\Program Files\MiKTeX 2.9\tex\latex\base\latin1.def" +File: latin1.def 2008/03/30 v1.1d Input encoding file +)) +("C:\Program Files\MiKTeX 2.9\tex\latex\graphics\graphicx.sty" +Package: graphicx 1999/02/16 v1.0f Enhanced LaTeX Graphics (DPC,SPQR) + +("C:\Program Files\MiKTeX 2.9\tex\latex\graphics\graphics.sty" +Package: graphics 2009/02/05 v1.0o Standard LaTeX Graphics (DPC,SPQR) + +("C:\Program Files\MiKTeX 2.9\tex\latex\graphics\trig.sty" +Package: trig 1999/03/16 v1.09 sin cos tan (DPC) +) +("C:\Program Files\MiKTeX 2.9\tex\latex\00miktex\graphics.cfg" +File: graphics.cfg 2007/01/18 v1.5 graphics configuration of teTeX/TeXLive +) +Package graphics Info: Driver file: pdftex.def on input line 91. + +("C:\Program Files\MiKTeX 2.9\tex\latex\pdftex-def\pdftex.def" +File: pdftex.def 2010/09/14 v0.05b Graphics/color for pdfTeX +\Gread@gobject=\count89 +)) +\Gin@req@height=\dimen103 +\Gin@req@width=\dimen104 +) +("C:\Program Files\MiKTeX 2.9\tex\latex\listings\listings.sty" +\lst@mode=\count90 +\lst@gtempboxa=\box27 +\lst@token=\toks17 +\lst@length=\count91 +\lst@currlwidth=\dimen105 +\lst@column=\count92 +\lst@pos=\count93 +\lst@lostspace=\dimen106 +\lst@width=\dimen107 +\lst@newlines=\count94 +\lst@lineno=\count95 +\lst@maxwidth=\dimen108 + +("C:\Program Files\MiKTeX 2.9\tex\latex\listings\lstmisc.sty" +File: lstmisc.sty 2007/02/22 1.4 (Carsten Heinz) +\c@lstnumber=\count96 +\lst@skipnumbers=\count97 +\lst@framebox=\box28 +) +("C:\Program Files\MiKTeX 2.9\tex\latex\listings\listings.cfg" +File: listings.cfg 2007/02/22 1.4 listings configuration +)) +Package: listings 2007/02/22 1.4 (Carsten Heinz) + +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\RPG-X2 Lua Documentation.aux") +LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 46. +LaTeX Font Info: ... okay on input line 46. +LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 46. +LaTeX Font Info: ... okay on input line 46. +LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 46. +LaTeX Font Info: ... okay on input line 46. +LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 46. +LaTeX Font Info: ... okay on input line 46. +LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 46. +LaTeX Font Info: ... okay on input line 46. +LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 46. +LaTeX Font Info: ... okay on input line 46. + +("C:\Program Files\MiKTeX 2.9\tex\context\base\supp-pdf.mkii" +[Loading MPS to PDF converter (version 2006.09.02).] +\scratchcounter=\count98 +\scratchdimen=\dimen109 +\scratchbox=\box29 +\nofMPsegments=\count99 +\nofMParguments=\count100 +\everyMPshowfont=\toks18 +\MPscratchCnt=\count101 +\MPscratchDim=\dimen110 +\MPnumerator=\count102 +\everyMPtoPDFconversion=\toks19 +) +\c@lstlisting=\count103 +LaTeX Font Info: External font `cmex10' loaded for size +(Font) <14.4> on input line 54. +LaTeX Font Info: External font `cmex10' loaded for size +(Font) <7> on input line 54. + [1 + + + +{C:/ProgramData/MiKTeX/2.9/pdftex/config/pdftex.map}] +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\RPG-X2 Lua Documentation.toc" +LaTeX Font Info: External font `cmex10' loaded for size +(Font) <10.95> on input line 2. +LaTeX Font Info: External font `cmex10' loaded for size +(Font) <8> on input line 2. +LaTeX Font Info: External font `cmex10' loaded for size +(Font) <6> on input line 2. + +[2 + +] [3]) +\tf@toc=\write3 + [4] +Chapter 1. + +Class scrreprt Warning: \float@addtolists detected! +(scrreprt) You should use the features of package `tocbasic' +(scrreprt) instead of \float@addtolists. +(scrreprt) Support for \float@addtolists may be removed from +(scrreprt) `scrreprt' soon . + +LaTeX Font Info: Try loading font information for OMS+cmr on input line 64. +("C:\Program Files\MiKTeX 2.9\tex\latex\base\omscmr.fd" +File: omscmr.fd 1999/05/25 v2.5h Standard LaTeX font definitions +) +LaTeX Font Info: Font shape `OMS/cmr/m/n' in size <10.95> not available +(Font) Font shape `OMS/cmsy/m/n' tried instead on input line 64. + +Overfull \hbox (31.01999pt too wide) in paragraph at lines 69--70 +\OT1/cmr/bx/n/10.95 var.function(var) \OT1/cmr/m/n/10.95 can be writ-ten as \OT +1/cmr/bx/n/10.95 var:function() \OT1/cmr/m/n/10.95 (ex-am-ple: \OT1/cmr/bx/n/10 +.95 ent.Remove(ent) + [] + +[5 + +] +Chapter 2. +[6 + +] [7] [8] [9] +Chapter 3. +[10 + +] +Chapter 4. +[11 + +] [12] [13] [14] [15] [16] [17] [18] [19] [20] [21] +Underfull \hbox (badness 10000) in paragraph at lines 626--630 + + [] + +[22] +Chapter 5. +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/HelloWorldGame.lua") +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/HelloWorldClient.lua") +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/HelloWorldClient1.lua") +[23 + +] ("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/HelloWorldClient2.lua") +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/HelloWorldClient3.lua") (" +C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/HelloWorldClientAll.lua") (" +C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/FindingEnts1.lua") +[24] ("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/FindingEnts2.lua") +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/FindingEnts3.lua") +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/SpawningEnts.lua") +[25] +Underfull \hbox (badness 10000) in paragraph at lines 708--710 + + [] + + +Underfull \hbox (badness 10000) in paragraph at lines 713--715 + + [] + + +Underfull \hbox (badness 10000) in paragraph at lines 718--720 + + [] + + +Underfull \hbox (badness 10000) in paragraph at lines 723--725 + + [] + + +Underfull \hbox (badness 10000) in paragraph at lines 728--730 + + [] + +[26] +Chapter 6. +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/fusToff.lua" [27 + +]) +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\examples/fusToff2.lua") [28] +("C:\stvoy\Code-DM\RPG-X2 Lua Documentation\RPG-X2 Lua Documentation.aux") ) +Here is how much of TeX's memory you used: + 4061 strings out of 494019 + 59081 string characters out of 3146494 + 150299 words of memory out of 3000000 + 7318 multiletter control sequences out of 15000+200000 + 9721 words of font info for 34 fonts, out of 3000000 for 9000 + 714 hyphenation exceptions out of 8191 + 35i,8n,40p,515b,1631s stack positions out of 5000i,500n,10000p,200000b,50000s + +Output written on "RPG-X2 Lua Documentation.pdf" (28 pages, 179749 bytes). +PDF statistics: + 132 PDF objects out of 1000 (max. 8388607) + 0 named destinations out of 1000 (max. 500000) + 1 words of extra memory for PDF output out of 10000 (max. 10000000) + diff --git a/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.lpr b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.lpr new file mode 100644 index 0000000..3233fb7 --- /dev/null +++ b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.lpr @@ -0,0 +1,93 @@ +[Files\0] +ActiveFilters= +Bookmark#0=648 +Bookmark#1=675 +Bookmark#2=0 +Bookmark#3=0 +Bookmark#4=0 +Bookmark#5=0 +Bookmark#6=0 +Bookmark#7=0 +Bookmark#8=0 +Bookmark#9=0 +CaretPos.X=64 +CaretPos.Y=730 +CharSet=def +FileName=rpg-x2 lua documentation.tex +FoldedLines= +IsMainFile=yes +Opened=yes +ScrollPos.X=0 +ScrollPos.Y=845 +SearchListCount=0 +ShowSyntax=yes +SpellChecking=yes +TabOrder=0 +UseThesaurus=yes +WrapLine=yes + +[Files] +Count=1 + +[Project] +ActiveFilters= + +[Project\Archive] +AutoArchive=no +AutoSet=1 +Components=as*.gpr;*.lpr:as*.tex;*.sty;*.cls;*.bib;*.mp;*.undo:as*.eps +Compression=1 +Dir=\Backup +Name=.zip + +[Project\DVI\10] +Page=0 +Zoom=9 + +[Project\DVI\1] +Page=0 +Zoom=9 + +[Project\DVI\2] +Page=0 +Zoom=9 + +[Project\DVI\3] +Page=0 +Zoom=9 + +[Project\DVI\4] +Page=0 +Zoom=9 + +[Project\DVI\5] +Page=0 +Zoom=9 + +[Project\DVI\6] +Page=0 +Zoom=9 + +[Project\DVI\7] +Page=0 +Zoom=9 + +[Project\DVI\8] +Page=0 +Zoom=9 + +[Project\DVI\9] +Page=0 +Zoom=9 + +[Project\DVI] +FileName=1 +FirstPage=0 +Magnifier=5 +Page=1 +PaperHeight=297 +PaperWidth=210 +Zoom=11 + +[Template] +Id=1 diff --git a/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.pdf b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.pdf new file mode 100644 index 0000000..03325b8 Binary files /dev/null and b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.pdf differ diff --git a/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.tex b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.tex new file mode 100644 index 0000000..de9022c --- /dev/null +++ b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.tex @@ -0,0 +1,763 @@ +\documentclass{scrreprt} +\usepackage[latin1]{inputenc} +\usepackage{graphicx} +\usepackage{listings} +\usepackage{listings} +% -*- latex -*- +% Definition of the Lua language for the listings package +% Time-stamp: <2008-11-30 15:27:16 rsmith> +% Written by Roland Smith and hereby placed in the public +% domain. +\lstdefinelanguage{lua} + {morekeywords={and,break,do,else,elseif,end,false,for,function,if,in,local, + nil,not,or,repeat,return,then,true,until,while}, + morekeywords={[2]arg,assert,collectgarbage,dofile,error,_G,getfenv, + getmetatable,ipairs,load,loadfile,loadstring,next,pairs,pcall,print, + rawequal,rawget,rawset,select,setfenv,setmetatable,tonumber,tostring, + type,unpack,_VERSION,xpcall}, + morekeywords={[2]coroutine.create,coroutine.resume,coroutine.running, + coroutine.status,coroutine.wrap,coroutine.yield}, + morekeywords={[2]module,require,package.cpath,package.load,package.loaded, + package.loaders,package.loadlib,package.path,package.preload, + package.seeall}, + morekeywords={[2]string.byte,string.char,string.dump,string.find, + string.format,string.gmatch,string.gsub,string.len,string.lower, + string.match,string.rep,string.reverse,string.sub,string.upper}, + morekeywords={[2]table.concat,table.insert,table.maxn,table.remove, + table.sort}, + morekeywords={[2]math.abs,math.acos,math.asin,math.atan,math.atan2, + math.ceil,math.cos,math.cosh,math.deg,math.exp,math.floor,math.fmod, + math.frexp,math.huge,math.ldexp,math.log,math.log10,math.max,math.min, + math.modf,math.pi,math.pow,math.rad,math.random,math.randomseed,math.sin, + math.sinh,math.sqrt,math.tan,math.tanh}, + morekeywords={[2]io.close,io.flush,io.input,io.lines,io.open,io.output, + io.popen,io.read,io.tmpfile,io.type,io.write,file:close,file:flush, + file:lines,file:read,file:seek,file:setvbuf,file:write}, + morekeywords={[2]os.clock,os.date,os.difftime,os.execute,os.exit,os.getenv, + os.remove,os.rename,os.setlocale,os.time,os.tmpname}, + sensitive=true, + morecomment=[l]{--}, + morecomment=[s]{--[[}{]]--}, + morestring=[b]", + morestring=[d]' + } +\lstset{numbers=left, numberstyle=\tiny, numbersep=5pt} +\lstset{language=lua} +\begin{document} +\title{ +\Huge RPG-X2 Lua Documentation +\author{ +Ubergames +Walter Julius 'GSIO01' Hennecke} +} +\maketitle +\newpage +\tableofcontents +\chapter{Introduction} +\label{intro} +\section{General Information} +\label{gen-info} +The RPG-X2 Lua Documentation documents and describes all Lua functions avaible in RPG-X2. The version you are reading right now is for \textbf{RPG-X2 version 2.2 beta 4.4.5}. The RPG-X2 Lua Documentation will be updated with every new release of RPG-X2. +\section{Prerequisites} +\label{preq} +\begin{itemize} + \item In Lua variables are not declared with their type. In order to provid you information of what type a variable is the types will be written infront of variables in italic (example: \textit{integer} \textbf{clientNum}). + \item There are three different types of function calls in RPG-X2 Lua.\begin{itemize} + \item Function calls from Lua base libraries (example: \textbf{\textbf{tostring(clientNum)}}). + \item Function calls from RPG-X2 libraries which have the library name infront \textbf{library.function()} (example: \textbf{entity.Spawn()}). + \item Function calls on variables. This is possible on entities and vectors for example (example: \textbf{ent.Remove(ent)}). + \item Function calls where the variable a function is called on is the first argument \textbf{var.function(var)} can be written as \textbf{var:function()} (example: \textbf{ent.Remove(ent)} is the same as \textbf{ent:Remove()}). +\end{itemize} +\end{itemize} +\chapter{Lua Hooks} +\label{lua-hooks} +\section{What is a Lua Hook} +\label{wia-lh} +A Lua Hook is a function that gets called when a specific event in the game logic happens. For example if the game is iniatialized in the game logic G\_InitGame function gets called. This function has a Lua Hook which means when the G\_InitGame function is called in the game logic the corresponding Lua function gets called as well. There are Lua Hooks with static function names and Lua Hooks with dynamic function names. +\section{Static Lua Hooks} +\label{s-lh} +Static Lua hooks always have the same function name. +\subsection{InitGame} +\label{init-game} +\textbf{InitGame(}\textit{integer} \textbf{leveltime,} \textit{integer} \textbf{randomssed, }\textit{integer} \textbf{restart)} +\newline +Gets called at game start or after a map\_restart command was issued. +\newline +\textbf{leveltime} current level time in milliseconds +\newline +\textbf{restart} is 1 when call is result of a map\_restart +\subsection{ShutdownGame} +\label{shutdown-game} +\textbf{ShutdownGame(}\textit{integer} \textbf{restart)} +\newline +Gets called when the game shuts down (disconnect, game is closed, map change, map restart). +\newline +\textbf{restart} is 1 when call is result of a map\_restart +\subsection{RunFrame} +\label{run-frame} +\textbf{RunFrame(}\textit{integer} \textbf{leveltime)} +\newline +Gets called everyframe. Should be used with cation because this is called every frame and the frametime is 50ms. +\newline +\textbf{leveltime} current leveltime in milliseconds +\subsection{GClientPrint} +\label{cli-print} +\textbf{GClientPrint(}\textit{string} \textbf{text, }\textit{entity }\textbf{client)} +\newline +Gets called when the game logic function G\_PrintfClient gets called. +\newline +\textbf{text} text that gets printed +\newline +\textbf{client} the client the text gets printed for +\subsection{GPrint} +\label{g-print} +\textbf{GPrint(}\textit{string}\textbf{ text)} +\newline +Gets called when the game logic function G\_Print is called. +\newline +\textbf{text} text that gets printed to the game console +\newpage +\section{Dynamic Lua Hooks} +\label{dyn-lh} +These hooks can have different functions names. All of the hooks are for etities. The function names for these are defined in radiant by key-value pairs. +As the function names depend on these pairs the function names for these hooks in this documentation are the keys that are used to define the function names in Radiant. +\subsection{luaThink} +\label{luaThink} +\textbf{luaThink(}\textit{entity}\textbf{ ent)} +\newline +Gets called each time the entity thinks. +\newline +\textbf{ent} the entity itself +\subsection{luaTouch} +\label{luaTouch} +Gets called each time the entity is touched. +\newline +\textbf{ent} the entity itself +\newline +\textbf{other} the entity that touched \textbf{ent} +\subsection{luaUse} +\label{luaUse} +Gets called each time the entity is used. +\newline +\textbf{ent} the entity itself +\newline +\textbf{activator} the entity that used \textbf{ent} +\subsection{luaHurt} +\label{luaHurt} +\textbf{luaHurt(}\textit{entity}\textbf{ ent, }\textit{entity}\textbf{ inflictor,}\textit{entity}\textbf{attacker)} +\newline +Gets called each time the entity gets hurt. +\newline +\textbf{ent} the entity itself +\newline +\textbf{inflictor} the inflictor +\newline +\textbf{attacker} the attacker +\subsection{luaDie} +\label{luaDie} +\textbf{luaDie(}\textit{entity} \textbf{ent,} \textit{entity} \textbf{inflictor,} \textit{entity} \textbf{attacker,} \textit{integer} \textbf{dmg,} \textit{integer}\textbf{ mod)} +\newline +Gets called when the entity dies. +\newline +\textbf{ent} the entity itself +\newline +\textbf{inflictor} the inflictor +\newline +\textbf{attacker} the attacker +\newline +\textbf{dmg} the ammount of damage +\newline +\textbf{mod} the means of death +\subsection{luaFree} +\label{luaFree} +\textbf{luaFree(}\textit{entity} \textbf{ent)} +\newline +Gets called when the entity is freed which means it is removed. +\newline +\textbf{ent} the entity itself +\subsection{luaReached} +\label{luaReached} +\textbf{luaReached(}\textit{entity }\textbf{ent)} +\newline +Gets called when movement of the entity has reached its endpoint. +\newline +\textbf{ent} the entity itself +\subsection{luaReachedAngular} +\label{luaReachedAngular} +\textbf{luaReachedAngular(}\textit{entity }\textbf{ent)} +\newline +Gets called when angular movement of the entity has reached its endangles. +\newline +\textbf{ent} the entity itself +\subsection{luaTrigger} +\label{luaTrigger} +\textbf{luaTrigger(}\textit{entity}\textbf{ ent, }\textit{entity}\textbf{ other)} +\newline +Gets called when the entity is triggered. Note that this is not the same as when the entity is used this is for trigger entities. +\newline +\textbf{ent} the entity itself +\newline +\textbf{other} the entity that triggerd \textbf{ent} +\subsection{luaSpawn} +\label{luaSpawn} +\textbf{lauSpawn(}\textit{entity}\textbf{ ent)} +\newline +Gets called when the entities spawn function is called. +\newline +\textbf{ent} the entity itself. +\newpage +\chapter{RPG-X2 Map Scripting} +\label{rpgx2-mapscripting} +\section{Map scripts} +\label{map-scripts} +Currently on script file can be loaded for each map. This script file has to be located in \textit{scripts/lua/} and must have the name \textit{.lua}. \textit{} is the name of the .map file and .bsp file. +\section{Calling Functions} +\label{map-callingfunction} +There are Dynamic Lua Hooks for use in Radiant (listed below und Dynamic Lua Hooks in this ducumentation). You can use these hooks on entities by adding the corresponding Lua Hook key and the function name as value to an entity. +\newline +For example if you want a function \textit{PrintText} to be called when a \textit{func\_usable} is used you have to add the key \textit{luaUse} and the value \textit{PrintText} to this entity. +\newpage +\chapter{RPG-X2 Lua Libraries} +\label{rpgx2-llibs} +\section{game} +\label{g} +This library provides acces to some game logic function such as G\_Printf \newline +and G\_ClientPrintf. +\subsection{game.Print} +\label{g-prnt} +\textbf{game.Print(}\textit{string}\textbf{ text)} +\newline +Prints \textbf{text} to the game console (the server console). +\subsection{game.ClientPrint} +\label{g-clientprint} +\textbf{game.ClientPrint(}\textit{integer}\textbf{ clientNum, }\textit{string}\textbf{ text)} +\newline +Prints \textbf{text} to the clients console that has the client number \textbf{clientNum}. If \textbf{clientNum} is -1 the text gets printed to all clients consoles. +\subsection{game.CenterPrint} +\label{g-centerprint} +\textbf{game.CenterPrint(}\textit{integer}\textbf{ clientNum, }\textit{string}\textbf{ text)} +\newline +Prints \textbf{text} to the center of the screen of the client with client number \textbf{clientNum}. If \textbf{clientNum} is -1 the text gets printed for all clients. +\subsection{game.MessagePrint} +\label{g-messagepritn} +\textbf{game.MessagePrint(}\textit{integer}\textbf{ clientNum, }\textit{string}\textbf{ text)} +\newline +Prints \textbf{text} to the lower right corner of the screen of the client with client number \textbf{clientNum}. If \textbf{clientNum} is -1 the text gets printed for all cleints. +\subsection{game.LevelTime} +\label{g-leveltime} +\textbf{game.LevelTime()} +Returns the curreten level time in milliseconds. +\subsection{game.SetGlobal} +\label{g-setglobal} +\textbf{game.SetGlobal(}\textit{string}\textbf{ name, value)} +\newline +Sets a global lua varible which is called \textbf{name} to \textbf{value}. Creates a new global variable if a variable of \textbf{name} does not exist. \textbf{value} can be of any type. +\subsection{game.GetGlobal} +\label{g-getglobal} +\textbf{game.GetGlobal(}\textit{string}\textbf{ name)} +\newline +Returns the value of the global variable \textbf{name}. Returns \textit{nil} if the variable does not exist. +\newpage +\section{qmath} +\label{qmath} +This library provides access to mathematical functions avaible in the game code. +\subsection{qmath.abs} +\label{qm-abs} +\textbf{qmath.abs(}\textit{float}\textbf{ f)} +\newline +Returns the integer part of \textbf{f}. +\subsection{qmath.sin} +\label{qm-sin} +\textbf{qmath.sin(}\textit{float}\textbf{ degree)} +\newline +Returns the sine of \textbf{degree}. +\subsection{qmath.cos} +\label{qm-cos} +\textbf{qmath.cos(}\textit{float}\textbf{ degree)} +\newline +Returns the cosine of \textbf{degree}. +\subsection{qmath.tan} +\label{qm-tan} +\textbf{qmath.tan(}\textit{float}\textbf{ degree)} +\newline +Returns the tangent of \textbf{degree}. +\subsection{qmath.asin} +\label{qm-asin} +\textbf{qmath.asin(}\textit{float}\textbf{ f)} +\newline +Returns the arcsine of \textbf{f}. +\subsection{qmath.acos} +\label{qm-acos} +\textbf{qmath.acos(}\textit{float}\textbf{ f)} +\newline +Returns the arccosine of \textbf{f}. +\subsection{qmath.atan} +\label{qm-atan} +\textbf{qmath.atan(}\textit{float}\textbf{ f)} +\newline +Returns the arctangent of \textbf{f}. +\subsection{qmath.floor} +\label{qm-floor} +\textbf{qmath.floor(}\textit{float}\textbf{ f)} +\newline +Returns the floored value of \textbf{f}. +\subsection{qmath.ceil} +\label{qm-ceil} +\textbf{qath.ceil(}\textit{float}\textbf{ f)} +\newline +Returns the ceiled value of \textbf{f}. +\subsection{qmath.fmod} +\label{qm-fmod} +\textbf{qmath.fmod(}\textit{float}\textbf{ f, }\textit{float}\textbf{ n)} +\newline +Returns the remainder of \begin{math}f/n.\end{math} +\subsection{qmath.modf} +\label{qm-modf} +\textbf{qmath.modf(}\textit{float}\textbf{ f)} +\newline +Breaks \begin{math}f\end{math} apart into its integer par and its fractional part. The fractional part is returned while the integer part is assigned to \begin{math}f\end{math} +\subsection{qmath.sqrt} +\label{qm-sqrt} +\textbf{qmath.sqrt(}\textit{float}\textbf{ f)} +\newline +Returns the square root of \textbf{f}. +\subsection{qmath.log} +\label{qm-log} +\textbf{qmath.log(}\textit{float}\textbf{ f)} +\newline +Returns the logarithm of \textbf{f}. +\subsection{qmath.log10} +\label{qm-log10} +\textbf{qmath.log10(}\textit{float}\textbf{ f)} +\newline +Returns the logarithm to the base of 10 of \textbf{f}. +\subsection{qmath.deg} +\label{qm-deg} +\textbf{qmath.deg(}\textit{float}\textbf{ radian)} +\newline +Converts from radian to degrees. +\subsection{qmath.rad} +\label{qm-rad} +\textbf{qmath.rad(}\textit{float}\textbf{ degree)} +\newline +Converts from degree to radian. +\subsection{qmath.frexp} +\label{qm-frexp} +\textbf{qmath.frexp(}\textit{float}\textbf{ f)} +\newline +Breaks \textbf{f} into its binary significand and an integral exponent for 2. +\newline +\begin{math}x=significand*2^exponent\end{math} +\subsection{qmath.ldexp} +\label{qm-ldexp} +\textbf{qmath.ldexp(}\textit{float}\textbf{ f, }\textit{float}\textbf{ n)} +\newline +Returns the result from multiplying \textbf{f} by 2 raised to the power of \textbf{n}. +\subsection{qmath.min} +\label{qm-min} +\textbf{qmath.min(}\textit{integer}\textbf{ array[])} +\newline +Return the lowest value in \textbf{array[]}. +\subsection{qmath.max} +\label{qm-max} +\textbf{qmath.max(}\textit{integer}\textbf{ array[])} +\newline +Return the highest value in \textbf{array[]}. +\subsection{qmath.random} +\label{qm-random} +\textbf{qmath.random()} +\newline +Returns random integers. +\subsection{qmath.crandom} +\label{qm-crandom} +\textbf{qmath.crandom()} +\newline +Returns random floats (crazy random function). +\newpage +\section{vector} +\label{vect} +This provides a new type vector along with mathematical functions for it. +\subsection{vector.New} +\label{vect-new} +\textbf{vector.New()} +\newline +Allocates and returns a new vector \begin{math}\left(\begin{array}{c} 0 \\ 0 \\ 0 \\ \end{array}\right)\end{math}. +\subsection{vector.Construct} +\label{vect-cons} +\textbf{vector.Construct(}\textit{float}\textbf{ x, }\textit{float}\textbf{ y, }\textit{float}\textbf{ z)} +\newline +Allocates and return a new vector \begin{math}\left(\begin{array}{c} x \\ y \\ z \\ \end{array}\right)\end{math}. +\subsection{vector.Set} +\label{vect-set} +\textbf{vector.Set(}\textit{vector}\textbf{ v, }\textit{float}\textbf{ x, }\textit{float}\textbf{ y, }\textit{float}\textbf{ z)} +\newline +Set the vector \textbf{v} to the specified values. +\subsection{vector.clear} +\label{vect-clear} +\textbf{vector.Clear(}\textit{vector}\textbf{ v)} +\newline +Clears \textbf{vector} by setting it to \begin{math}\left(\begin{array}{c} 0 \\ 0 \\ 0 \\ \end{array}\right)\end{math}. +\subsection{vector.Add} +\label{vect-add} +\textbf{vector.Add(}\textit{vector}\textbf{ a, }\textit{vector}\textbf{ b, }\textit{vector}\textbf{ c)} +\newline +Adds \textbf{a} and \textbf{b} ans stores the result in \textbf{c}. +\subsection{vector.Substract} +\label{vect-sub} +\textbf{vector.Subtract(}\textit{vector}\textbf{ a, }\textit{vector}\textbf{ b, }\textit{vector}\textbf{ c)} +\newline +Subtracts \textbf{b} from \textbf{a} and stores the result in \textbf{c}. +\subsection{vector.Scale} +\label{vect-scale} +\textbf{vector.Scale(}\textit{vector}\textbf{ a, }\textit{float}\textbf{ b, }\textit{vector}\textbf{ c)} +\newline +Scales \textbf{a} by the value of \textbf{b} and stores the result in \textbf{c}. +\subsection{vector.Length} +\label{vect-length} +\textbf{vector.Length(}\textit{vector}\textbf{ a)} +\newline +Returns the length of \textbf{a}. +\subsection{vector.Normalize} +\label{vect-norm} +\textbf{vector.Normalize(}\textit{vector}\textbf{ a)} +\newline +Normalizes \textbf{a}. +\subsection{vector.RotateAroundPoint} +\label{vect-rotarndpnt} +\textbf{vector.RotateAroundPoint(}\textit{vector}\textbf{ dest, }\textit{vector}\textbf{ dir, }\textit{vector}\textbf{ point, }\textit{float}\textbf{ degrees)} +\newline +Rotates \textbf{point} around a given vector. +\newline +\textbf{dir} vector around which to rotate (must be normalized) +\newline +\textbf{point} point to be rotated +\newline +\textbf{degrees} how many degrees to rotate the point by +\newline +\textbf{dst} point after totation +\subsection{vector.Perpendicular} +\label{vect-Perpendicular} +\textbf{vector.Perpendicular(}\textit{vector}\textbf{ dest, }\textit{vector}\textbf{ src)} +\newline +Finds a vector perpendicular to the source vector. +\textbf{src} source vector +\textbf{dest} a vector that is perpendicular to \textbf{src} (the result is stored here) +\newpage +\section{entity} +\label{enty} +This library holds function for entities. All functions listed with entity infront here are calls from the library. All functions listed with ent are called on a variable of the type entity. +\subsection{entity.Find} +\label{enty-find} +\textbf{entity.Find(}\textit{string}\textbf{ targetname)} +\newline +Returns the first entity found that has a targetname of \textbf{targetname}. +\subsection{entity.FindNumber} +\label{enty.findnumber} +\textbf{entity.FindNumber(}\textit{integer}\textbf{ entnum)} +\newline +Returns the entity with the entity number \textbf{entnum}. +\subsection{entity.FindBModel} +\label{enty-findbmodel} +\textbf{entity.FindBModel(}\textit{integer}\textbf{ bmodelnum)} +\newline +Returns the entity with the brush model *\textbf{bmodelnumber}. This is the only failsafe way to find brush entities as the entity number is diffrent when you load a map local or join a server. +\subsection{ent.GetNumber} +\label{enty-getnumber} +\textbf{ent.GetNumber(}\textit{entity}\textbf{ ent)} or \textbf{ent:GetNumber()} +\newline +Returns the entity number of the entity. +\subsection{ent.SetKeyValue} +\label{enty-setkeyvalue} +\textbf{ent.SetKeyValue(}\textit{entity}\textbf{ ent, }\textit{string}\textbf{ key, }\textit{string}\textbf{ value)} or \textbf{ent:SetKeyValue(}\textit{string}\textbf{ key, }\textit{string}\textbf{ value)} +\newline +Sets a key-value pair for \textbf{ent} like in Radiant. Only works if the \textit{key} is part of \textit{fields\_t} (predefined keys). +\subsection{entity.Remove} +\label{enty-remove} +\textbf{entity.Remove(}\textit{entity}\textbf{ ent)} +\newline +Removes/frees \textbf{ent}. +\subsection{ent.GetOrigin} +\label{enty-getorigin} +\textbf{ent.GetOrigin(}\textit{entity}\textbf{ ent)} or \textbf{ent:GetOrigin()} +\newline +Returns the origin of \textbf{ent} as vector. +\subsection{ent.IsClient} +\label{enty-isclient} +\textbf{ent.IsClient(}\textit{entity}\textbf{ ent)} or \textbf{ent:IsClient()} +\newline +Returns boolean. True if \textbf{ent} is a client. +\subsection{ent.GetClientname} +\label{enty-getclientname} +\textbf{ent.GetClientname(}\textit{entity}\textbf{ ent)} or \textbf{ent:GetClientname()} +\newline +Returns the clientname of \textbf{ent}. +\subsection{ent.GetClassname} +\label{enty-getclassname} +\textbf{ent.GetClassname(}\textit{entity}\textbf{ ent)} or \textbf{ent:GetClassname()} +\newline +Returns the classname of \textbf{ent}. +\subsection{ent.SetClassname} +\label{enty-setclassname} +\textbf{ent.SetClassname(}\textit{entity}\textbf{ ent, }\textit{string}\textbf{ classname)} or +\newline +\textbf{ent:SetClassname(}\textit{string}\textbf{ classname)} +\newline +Sets the classname of \textbf{ent} to \textbf{classname}. +\subsection{ent.GetTargetname} +\label{enty-gettargetname} +\textbf{ent.GetTargetname(}\textit{entity}\textbf{ ent)} or \textbf{ent:GetTargetname()} +\newline +Returns the targetname of \textbf{ent}. +\subsection{ent.SetupTrigger} +\label{enty-setuptrigger} +\textbf{ent.SetupTrigger(}\textit{enttiy}\textbf{ ent)} or \textbf{ent:SetupTrigger()} +\newline +Does some setup for entities spawned by script that are to be used as trigger. +\subsection{entity.GetTarget} +\label{enty-gettarget} +\textbf{entity.GetTarget(}\textit{entity}\textbf{ ent)} +Returns the target of \textbf{ent}. +\subsection{entity.Use} +\label{enty-use} +\textbf{entity.Use(}\textit{entity}\textbf{ ent)} +\newline +Uses \textbf{ent}. +\subsection{entity.Spawn} +\label{enty-spawn} +\textbf{entity.spawn()} +\newline +Tries to spawn a new entity and returns it. If no new entity can be spawned \textit{nil} is returned. +\subsection{entiy.CallSpawn} +\label{enty-callspawn} +\textbf{entity.CallSpawn(}\textit{entity}\textbf{ ent)} +\newline +Calls the game logic spawn function for the class of \textbf{ent}. +\subsection{entity.DelayedCallSpawn} +\label{enty-delayedcallspawn} +\textbf{entity.DelayedCallSpawn(}\textit{entity}\textbf{ ent, }\textit{integer}\textbf{ delay)} +\newline +Calls the game logic spawn function for the class of \textbf{ent} after a delay. +\textbf{delay} delay in milliseconds +\subsection{entity.RemoveSpawns} +\label{enty-removespawns} +\textbf{entity.RemoveSpawns()} +\newline +Removes all spawn points from the map. +\subsection{ent.Lock} +\label{enty-lock} +\textbf{ent.Lock(}\textit{entity}\textbf{ ent)} +\newline +Looks the entity. Works with anything that can be locked (doors, turbolifts, usables, ...). +\subsection{ent.Unlock} +\label{enty-unlock} +\textbf{ent.Unlock(}\textit{entity}\textbf{ ent)} +\newline +Unlocks the entity. Works with anything that can be locked (doors, turbolifts, usables, ...). +\subsection{ent.IsLocked} +\label{enty-locked} +\textbf{ent.IsLocked(}\textit{entity}\textbf{ ent)} +\newline +Returns \textbf{true} if entity is locked else it returns \textbf{false}. +\subsection{ent.GetParm} +\label{enty-getparm} +\textbf{ent.GetParm(}\textit{entity }\textbf{ent, }\textit{integer }\textbf{parm)} +\newline +Returns one of the four luaParms of an entity as string. Returns \textbf{nil} if the choosen luaParm is not set. +\newline +\textbf{parm} to return (number between 1 and 4) +\subsection{ent.SetParm} +\label{enty-setparm} +\textbf{ent.SetParm(}\textit{entity }\textbf{ent, }\textit{integer }\textbf{parm, }\textit{string }\textbf{value)} +\newline +Sets one oth the four luaParms of an entity to \textbf{value}. +\newline +\textbf{parm} parm to be set (number between 1 and 4) +\newline +\textbf{value} value to set the parm to +\newpage +\section{mover} +\label{mver} +Important note: always call mover.Halt or mover.HaltAngles before you move a mover again otherwise the movement wont work correctly. +\subsection{mover.Halt} +\label{mver-halt} +\textbf{mover.Halt(}\textit{entity}\textbf{ ent)} +\newline +Stops movement immediately. +\subsection{mover.HaltAngles} +\label{mver-haltangles} +\textbf{mover.HaltAngles(}\textit{entity}\textbf{ ent)} +\newline +Stops angular movement immediately. +\subsection{mover.AsTrain} +\label{mver-astrain} +\textbf{mover.AsTrain(}\textit{entity}\textbf{ mover, }\textit{entity}\textbf{ target, }\textit{float}\textbf{ speed)} +\newline +Moves an entity like a \textit{func\_train} entity. Targets have to be \textit{path\_corner} entities. +\newline +\textbf{target} the first \textit{path\_corner} to move to. +\subsection{mover.SetAngles} +\label{mver-setangles} +\textbf{mover.SetAngles(}\textit{entity}\textbf{ ent, }\textit{vector}\textbf{ angles)} or \textbf{mover.SetAngles(}\textit{entity}\textbf{ ent, }\textit{float}\textbf{ x, }\textit{float}\textbf{ y, }\textit{float}\textbf{ z)} +\newline +Sets the angles of \textbf{ent} to the secified value(s). +\subsection{mover.SetPosition} +\label{mver-setposition} +\textbf{mover.SetPosition(}\textit{entity}\textbf{ ent, }\textit{vector}\textbf{ pos)} or \textbf{mover.SetPosition(}\textit{entity}\textbf{ ent, }\textit{float}\textbf{ x, }\textit{float}\textbf{ y, }\textit{float}\textbf{ z)} +\newline +Set the position of \textbf{ent} to the specified value(s). +\subsection{mover.ToAngles} +\label{mver-toangles} +\textbf{mover.ToAngles}(\textit{entity}\textbf{ ent, }\textit{float}\textbf{ speed, }\textit{vector}\textbf{ angles)} or \textbf{mover.ToAngles(}\textit{entity}\textbf{ ent, }\textit{float}\textbf{ speed, }\textit{float}\textbf{ x, }\textit{float}\textbf{ y, }\textit{float}\textbf{ z)} +\newline +Rotates \textbf{ent} to the specified angles. +\subsection{mover.ToPosition} +\label{mver-toposition} +\textbf{mover.ToPosition(}\textit{entity}\textbf{ ent, }\textit{float}\textbf{ speed, }\textit{vector}\textbf{ angles)} or +\newline +\textbf{mover.ToPosition(}\textit{entity}\textbf{ ent, }\textit{float}\textbf{ speed, }\textit{float}\textbf{ x, }\textit{float}\textbf{ y, }\textit{float}\textbf{ z)} +\newline +Moves \textbf{ent} to the specified position. +\newpage +\section{sound} +\label{sound} +This library adds function to play and handle sounds. +\subsection{Sound Channels} +\label{sound-chan} +In some function of the sound library you will be asked to specify a sound channel. Genral it will be ok to use CHAN\_AUTO and let the engine choose the channel. Anyway you will be able to choose channels yourself. +\newline +Here is a table with the diffrent channels and their numbers to use in functions: +\newline +\begin{center} +\begin{tabular}[c]{| c | c |} +\hline +CHAN\_AUTO & 0 \\ +\hline +CHAN\_LOCAL & 1 \\ +\hline +CHAN\_WEAPON & 2 \\ +\hline +CHAN\_VOICE & 3 \\ +\hline +CHAN\_ITEM & 4 \\ +\hline +CHAN\_BODY & 5 \\ +\hline +CHAN\_LOCAL\_SOUND & 6 \\ +\hline +CHAN\_ANNOUNCER & 7 \\ +\hline +CHAN\_MENU1 & 8 \\ +\hline +\end{tabular} +\end{center} +\subsection{sound.PlaySound} +\label{snd-playsnd} +\textbf{sound.PlaySound(}\textit{entity}\textbf{ ent, }\textit{integer}\textbf{ chan, }\textit{string}\textbf{ sound)} +\newline +Plays the sound file \textbf{sound} using the channel \textbf{chan} on the entity \textbf{ent}. +\newpage +\chapter{Examples} +\label{examples} +This section of the manual contains script examples which may help you to understand how certain functions should be used. +\section{Example 1 - HelloWorld} +\label{example1} +This is a must have example I think as it always is there for any programming language you learn. +\subsection{Hello World for game} +\label{example11} +\lstinputlisting[frame=single, label=helloworldgame, caption=Hello World for game]{examples/HelloWorldGame.lua} +As you might not this is a function for luaUse (you can tell that from the function head). +\subsection{Hello World for a client} +\label{example12} +\lstinputlisting[frame=single, label=helloworldclient, caption=Hello World for client]{examples/HelloWorldClient.lua} +As you might not this is a function for luaUse (you can tell that from the function head). +\lstinputlisting[frame=single, label=helloworldclient1, caption=First function]{examples/HelloWorldClient1.lua} +This function prints a message to the clients console. +\newline +\lstinline|activator:GetNumber()| gets the entity number of the activator which in this case is the client number as well. +\newline +\lstinline|activator:GetClientname()| gets the clients clientname. +\lstinputlisting[frame=single, label=helloworldclient2, caption=Second function]{examples/HelloWorldClient2.lua} +This function prints a message to the center of the screen of a client. +\lstinputlisting[frame=single, label=helloworldclient3, caption=Third function]{examples/HelloWorldClient3.lua} +This function prints a message to the lower right corner of the clients screen. +\subsection{Hello World for all clients} +\label{example13} +\lstinputlisting[frame=single, label=helloworldclientall, caption=Hello World for all client]{examples/HelloWorldClientAll.lua} +This is very similar to the previous example the only difference is that instead of a client number -1 is the first arguments which results in the message to be printed to all clients. +\section{Example 2 - Finding Entities} +\label{example2} +These examples will show the different ways of finding an entity. +\subsection{Finding entities by their targetnames} +\label{example21} +\lstinputlisting[frame=single, label=findents1, caption=Find an entity by its targetname]{examples/FindingEnts1.lua} +You should note that \lstinline|entity.Find()| only returns the first entity found which means if there are multiple entities with the same targetname and the one found first isn't yours you'll be unable to find the wanted entity by this way. +\newline +Also besides showing you how to find an entity you also can see howto use local variables in function here. +\subsection{Finding entities by their entity number} +\label{example22} +\lstinputlisting[frame=single, label=findents2, caption=Find an entity by its entity number]{examples/FindingEnts2.lua} +This is a quite failsafe way to find an entity there is just one thing you have to note: The entity number for an entity when the map is loaded locally is not the same as the entity number for an entity when running a dedicated server. +\subsection{Finding entities by thier brush model} +\label{example23} +\lstinputlisting[frame=single, label=findents3, caption=Find an entity by its brush model]{examples/FindingEnts3.lua} +This only works for brush entities of course but for these it is absolutly failsafe. +\section{Example 3 - Spawning entities} +This example shows you how to spawn entities from scripting. You can spawn almost all non brush entities as well as some brush entities that don't require a visible brush model (e.g. triggers). +\lstinputlisting[frame=single, label=spawnents, caption=Spawning an entity]{examples/SpawningEnts.lua} +\newpage +So what does what and why? +\newline +\begin{lstlisting} +local ent = entity.Spawn() +\end{lstlisting} +This tries to spawn a new entity and assign it to \lstinline|ent|. +\newline +\begin{lstlisting} +if ent == nil then return +\end{lstlisting} +This is a check to make sure that a new entity was sucessfully spawned. If that is not the case the further execution of the function is stopped. +\newline +\begin{lstlisting} +ent:SetKeyValue("classname", "info_notnull"); +\end{lstlisting} +This sets the classname and by this the entity is turned to an entity of a specific type. +\newline +\begin{lstlisting} +mover:SetPosition(0, 0, 0); +\end{lstlisting} +This sets the origin of the entity. +\newline +\begin{lstlisting} +entity.CallSpawn(ent); +\end{lstlisting} +This calls the spawn function of the entity. +\chapter{How to ...} +\label{howto} +\section{add RPG-X2 Turbolifts to older maps} +\label{howto-x2turbo} +Comming soon ... +\section{add Transporters with ui\_transporter to older maps} +\label{howto-uitrans} +Comming soon ... +\section{convert func\_usable force field from older maps to func\_forcefield} +\label{howto-usabletoforcefield} +This HowTo shows you how you can convert a func\_usable to a func\_forcefield. Before we can start scripting we need to find out some things about the usable: +\begin{itemize} + \item How can you identify the usable 100 per cent failsafe. + \item What are the current spawnflags of the entity. +\end{itemize} +You can obtain the information by doing the following things. +\begin{itemize} + \item Start RPG-X2 and laod the map. + \item Login as admin or change to admin class. + \item Goto the func\_usable and make sure it is visible (the force field is activated. + \item Target the usable with your crosshair. + \item Open console and type \textbf{getEntInfo}. +\end{itemize} +You'll get a list of usefull information. Now if the entity has a targetname the next thing to do is to check if it is the only one with it. While you are still in console type \textbf{getEntByTargetname} followed by the targetname. +If only one entity is listed the func\_usable is the only one with this targetname and you are done otherwise just use the brushmodel of the func\_usable. +The next step is to see if the spawnflags are ok for your needs. This means check if any spawnflags of func\_forcefield are included you don't want or if some are missing. +No you start scripting. The best place to do this entity conversion is the \textbf{InitGame} function because this function is already called during map loading. +\lstinputlisting[frame=single, label=fustoff, caption=Example 1]{examples/fusToff.lua} +\lstinputlisting[frame=single, label=fustoff2, caption=Example 2]{examples/fusToff2.lua} +\end{document} diff --git a/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.tex.undo b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.tex.undo new file mode 100644 index 0000000..65cc177 Binary files /dev/null and b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.tex.undo differ diff --git a/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.toc b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.toc new file mode 100644 index 0000000..712767d --- /dev/null +++ b/RPG-X2 Lua Documentation/RPG-X2 Lua Documentation.toc @@ -0,0 +1,119 @@ +\contentsline {chapter}{\numberline {1}Introduction}{5} +\contentsline {section}{\numberline {1.1}General Information}{5} +\contentsline {section}{\numberline {1.2}Prerequisites}{5} +\contentsline {chapter}{\numberline {2}Lua Hooks}{6} +\contentsline {section}{\numberline {2.1}What is a Lua Hook}{6} +\contentsline {section}{\numberline {2.2}Static Lua Hooks}{6} +\contentsline {subsection}{\numberline {2.2.1}InitGame}{6} +\contentsline {subsection}{\numberline {2.2.2}ShutdownGame}{6} +\contentsline {subsection}{\numberline {2.2.3}RunFrame}{6} +\contentsline {subsection}{\numberline {2.2.4}GClientPrint}{6} +\contentsline {subsection}{\numberline {2.2.5}GPrint}{7} +\contentsline {section}{\numberline {2.3}Dynamic Lua Hooks}{8} +\contentsline {subsection}{\numberline {2.3.1}luaThink}{8} +\contentsline {subsection}{\numberline {2.3.2}luaTouch}{8} +\contentsline {subsection}{\numberline {2.3.3}luaUse}{8} +\contentsline {subsection}{\numberline {2.3.4}luaHurt}{8} +\contentsline {subsection}{\numberline {2.3.5}luaDie}{8} +\contentsline {subsection}{\numberline {2.3.6}luaFree}{9} +\contentsline {subsection}{\numberline {2.3.7}luaReached}{9} +\contentsline {subsection}{\numberline {2.3.8}luaReachedAngular}{9} +\contentsline {subsection}{\numberline {2.3.9}luaTrigger}{9} +\contentsline {subsection}{\numberline {2.3.10}luaSpawn}{9} +\contentsline {chapter}{\numberline {3}RPG-X2 Map Scripting}{10} +\contentsline {section}{\numberline {3.1}Map scripts}{10} +\contentsline {section}{\numberline {3.2}Calling Functions}{10} +\contentsline {chapter}{\numberline {4}RPG-X2 Lua Libraries}{11} +\contentsline {section}{\numberline {4.1}game}{11} +\contentsline {subsection}{\numberline {4.1.1}game.Print}{11} +\contentsline {subsection}{\numberline {4.1.2}game.ClientPrint}{11} +\contentsline {subsection}{\numberline {4.1.3}game.CenterPrint}{11} +\contentsline {subsection}{\numberline {4.1.4}game.MessagePrint}{11} +\contentsline {subsection}{\numberline {4.1.5}game.LevelTime}{11} +\contentsline {subsection}{\numberline {4.1.6}game.SetGlobal}{11} +\contentsline {subsection}{\numberline {4.1.7}game.GetGlobal}{12} +\contentsline {section}{\numberline {4.2}qmath}{13} +\contentsline {subsection}{\numberline {4.2.1}qmath.abs}{13} +\contentsline {subsection}{\numberline {4.2.2}qmath.sin}{13} +\contentsline {subsection}{\numberline {4.2.3}qmath.cos}{13} +\contentsline {subsection}{\numberline {4.2.4}qmath.tan}{13} +\contentsline {subsection}{\numberline {4.2.5}qmath.asin}{13} +\contentsline {subsection}{\numberline {4.2.6}qmath.acos}{13} +\contentsline {subsection}{\numberline {4.2.7}qmath.atan}{13} +\contentsline {subsection}{\numberline {4.2.8}qmath.floor}{13} +\contentsline {subsection}{\numberline {4.2.9}qmath.ceil}{14} +\contentsline {subsection}{\numberline {4.2.10}qmath.fmod}{14} +\contentsline {subsection}{\numberline {4.2.11}qmath.modf}{14} +\contentsline {subsection}{\numberline {4.2.12}qmath.sqrt}{14} +\contentsline {subsection}{\numberline {4.2.13}qmath.log}{14} +\contentsline {subsection}{\numberline {4.2.14}qmath.log10}{14} +\contentsline {subsection}{\numberline {4.2.15}qmath.deg}{14} +\contentsline {subsection}{\numberline {4.2.16}qmath.rad}{14} +\contentsline {subsection}{\numberline {4.2.17}qmath.frexp}{14} +\contentsline {subsection}{\numberline {4.2.18}qmath.ldexp}{15} +\contentsline {subsection}{\numberline {4.2.19}qmath.min}{15} +\contentsline {subsection}{\numberline {4.2.20}qmath.max}{15} +\contentsline {subsection}{\numberline {4.2.21}qmath.random}{15} +\contentsline {subsection}{\numberline {4.2.22}qmath.crandom}{15} +\contentsline {section}{\numberline {4.3}vector}{16} +\contentsline {subsection}{\numberline {4.3.1}vector.New}{16} +\contentsline {subsection}{\numberline {4.3.2}vector.Construct}{16} +\contentsline {subsection}{\numberline {4.3.3}vector.Set}{16} +\contentsline {subsection}{\numberline {4.3.4}vector.clear}{16} +\contentsline {subsection}{\numberline {4.3.5}vector.Add}{16} +\contentsline {subsection}{\numberline {4.3.6}vector.Substract}{16} +\contentsline {subsection}{\numberline {4.3.7}vector.Scale}{16} +\contentsline {subsection}{\numberline {4.3.8}vector.Length}{17} +\contentsline {subsection}{\numberline {4.3.9}vector.Normalize}{17} +\contentsline {subsection}{\numberline {4.3.10}vector.RotateAroundPoint}{17} +\contentsline {subsection}{\numberline {4.3.11}vector.Perpendicular}{17} +\contentsline {section}{\numberline {4.4}entity}{18} +\contentsline {subsection}{\numberline {4.4.1}entity.Find}{18} +\contentsline {subsection}{\numberline {4.4.2}entity.FindNumber}{18} +\contentsline {subsection}{\numberline {4.4.3}entity.FindBModel}{18} +\contentsline {subsection}{\numberline {4.4.4}ent.GetNumber}{18} +\contentsline {subsection}{\numberline {4.4.5}ent.SetKeyValue}{18} +\contentsline {subsection}{\numberline {4.4.6}entity.Remove}{18} +\contentsline {subsection}{\numberline {4.4.7}ent.GetOrigin}{18} +\contentsline {subsection}{\numberline {4.4.8}ent.IsClient}{19} +\contentsline {subsection}{\numberline {4.4.9}ent.GetClientname}{19} +\contentsline {subsection}{\numberline {4.4.10}ent.GetClassname}{19} +\contentsline {subsection}{\numberline {4.4.11}ent.SetClassname}{19} +\contentsline {subsection}{\numberline {4.4.12}ent.GetTargetname}{19} +\contentsline {subsection}{\numberline {4.4.13}ent.SetupTrigger}{19} +\contentsline {subsection}{\numberline {4.4.14}entity.GetTarget}{19} +\contentsline {subsection}{\numberline {4.4.15}entity.Use}{19} +\contentsline {subsection}{\numberline {4.4.16}entity.Spawn}{19} +\contentsline {subsection}{\numberline {4.4.17}entiy.CallSpawn}{20} +\contentsline {subsection}{\numberline {4.4.18}entity.DelayedCallSpawn}{20} +\contentsline {subsection}{\numberline {4.4.19}entity.RemoveSpawns}{20} +\contentsline {subsection}{\numberline {4.4.20}ent.Lock}{20} +\contentsline {subsection}{\numberline {4.4.21}ent.Unlock}{20} +\contentsline {subsection}{\numberline {4.4.22}ent.IsLocked}{20} +\contentsline {subsection}{\numberline {4.4.23}ent.GetParm}{20} +\contentsline {subsection}{\numberline {4.4.24}ent.SetParm}{20} +\contentsline {section}{\numberline {4.5}mover}{21} +\contentsline {subsection}{\numberline {4.5.1}mover.Halt}{21} +\contentsline {subsection}{\numberline {4.5.2}mover.HaltAngles}{21} +\contentsline {subsection}{\numberline {4.5.3}mover.AsTrain}{21} +\contentsline {subsection}{\numberline {4.5.4}mover.SetAngles}{21} +\contentsline {subsection}{\numberline {4.5.5}mover.SetPosition}{21} +\contentsline {subsection}{\numberline {4.5.6}mover.ToAngles}{21} +\contentsline {subsection}{\numberline {4.5.7}mover.ToPosition}{21} +\contentsline {section}{\numberline {4.6}sound}{22} +\contentsline {subsection}{\numberline {4.6.1}Sound Channels}{22} +\contentsline {subsection}{\numberline {4.6.2}sound.PlaySound}{22} +\contentsline {chapter}{\numberline {5}Examples}{23} +\contentsline {section}{\numberline {5.1}Example 1 - HelloWorld}{23} +\contentsline {subsection}{\numberline {5.1.1}Hello World for game}{23} +\contentsline {subsection}{\numberline {5.1.2}Hello World for a client}{23} +\contentsline {subsection}{\numberline {5.1.3}Hello World for all clients}{24} +\contentsline {section}{\numberline {5.2}Example 2 - Finding Entities}{24} +\contentsline {subsection}{\numberline {5.2.1}Finding entities by their targetnames}{24} +\contentsline {subsection}{\numberline {5.2.2}Finding entities by their entity number}{25} +\contentsline {subsection}{\numberline {5.2.3}Finding entities by thier brush model}{25} +\contentsline {section}{\numberline {5.3}Example 3 - Spawning entities}{25} +\contentsline {chapter}{\numberline {6}How to ...}{27} +\contentsline {section}{\numberline {6.1}add RPG-X2 Turbolifts to older maps}{27} +\contentsline {section}{\numberline {6.2}add Transporters with ui\_transporter to older maps}{27} +\contentsline {section}{\numberline {6.3}convert func\_usable force field from older maps to func\_forcefield}{27} diff --git a/RPG-X2 Lua Documentation/RPG-X2 Lua Dokumentation WIP.pdf b/RPG-X2 Lua Documentation/RPG-X2 Lua Dokumentation WIP.pdf new file mode 100644 index 0000000..00a2cf6 Binary files /dev/null and b/RPG-X2 Lua Documentation/RPG-X2 Lua Dokumentation WIP.pdf differ diff --git a/RPG-X2 Lua Documentation/examples/FindingEnts1.lua b/RPG-X2 Lua Documentation/examples/FindingEnts1.lua new file mode 100644 index 0000000..847c677 --- /dev/null +++ b/RPG-X2 Lua Documentation/examples/FindingEnts1.lua @@ -0,0 +1,4 @@ +function Example() + local ent; + ent = entity.Find("doorbell"); +end \ No newline at end of file diff --git a/RPG-X2 Lua Documentation/examples/FindingEnts2.lua b/RPG-X2 Lua Documentation/examples/FindingEnts2.lua new file mode 100644 index 0000000..f170e18 --- /dev/null +++ b/RPG-X2 Lua Documentation/examples/FindingEnts2.lua @@ -0,0 +1,4 @@ +function Example() + local ent; + ent = entity.FindNumber(22); +end \ No newline at end of file diff --git a/RPG-X2 Lua Documentation/examples/FindingEnts3.lua b/RPG-X2 Lua Documentation/examples/FindingEnts3.lua new file mode 100644 index 0000000..9d982ba --- /dev/null +++ b/RPG-X2 Lua Documentation/examples/FindingEnts3.lua @@ -0,0 +1,4 @@ +function Example() + local ent; + ent = entity.FindBModel(22); +end \ No newline at end of file diff --git a/RPG-X2 Lua Documentation/examples/HelloWorldClient.lua b/RPG-X2 Lua Documentation/examples/HelloWorldClient.lua new file mode 100644 index 0000000..adf0ec3 --- /dev/null +++ b/RPG-X2 Lua Documentation/examples/HelloWorldClient.lua @@ -0,0 +1,8 @@ +function HelloWorld(ent, other, activator) + game.ClientPrint(activator:GetNumber(), + "Hello " .. activator:GetClientname()); + game.CenterPrint(activator:GetNumber(), + "Hello " .. activator:GetClientname()); + game.MessagePrint(activator:GetNumber(), + "Hello " .. activator:GetClientname()); +end \ No newline at end of file diff --git a/RPG-X2 Lua Documentation/examples/HelloWorldClient1.lua b/RPG-X2 Lua Documentation/examples/HelloWorldClient1.lua new file mode 100644 index 0000000..5293823 --- /dev/null +++ b/RPG-X2 Lua Documentation/examples/HelloWorldClient1.lua @@ -0,0 +1,2 @@ +game.ClientPrint(activator:GetNumber(), + "Hello " .. activator:GetClientname()); diff --git a/RPG-X2 Lua Documentation/examples/HelloWorldClient2.lua b/RPG-X2 Lua Documentation/examples/HelloWorldClient2.lua new file mode 100644 index 0000000..056f74b --- /dev/null +++ b/RPG-X2 Lua Documentation/examples/HelloWorldClient2.lua @@ -0,0 +1,2 @@ +game.CenterPrint(activator:GetNumber(), + "Hello " .. activator:GetClientname()); \ No newline at end of file diff --git a/RPG-X2 Lua Documentation/examples/HelloWorldClient3.lua b/RPG-X2 Lua Documentation/examples/HelloWorldClient3.lua new file mode 100644 index 0000000..96c5cc5 --- /dev/null +++ b/RPG-X2 Lua Documentation/examples/HelloWorldClient3.lua @@ -0,0 +1,2 @@ +game.MessagePrint(activator:GetNumber(), + "Hello " .. activator:GetClientname()); \ No newline at end of file diff --git a/RPG-X2 Lua Documentation/examples/HelloWorldClientAll.lua b/RPG-X2 Lua Documentation/examples/HelloWorldClientAll.lua new file mode 100644 index 0000000..aa011e4 --- /dev/null +++ b/RPG-X2 Lua Documentation/examples/HelloWorldClientAll.lua @@ -0,0 +1,5 @@ +function HelloWorld(ent, other, activator) + game.ClientPrint(-1, "Hello all"); + game.CenterPrint(-1, "Hello all"); + game.MessagePrint(-1, "Hello all"); +end \ No newline at end of file diff --git a/RPG-X2 Lua Documentation/examples/HelloWorldGame.lua b/RPG-X2 Lua Documentation/examples/HelloWorldGame.lua new file mode 100644 index 0000000..87a7f7d --- /dev/null +++ b/RPG-X2 Lua Documentation/examples/HelloWorldGame.lua @@ -0,0 +1,3 @@ +function HelloWorld(ent, other, activator) + game.Print("Hello World"); +end \ No newline at end of file diff --git a/RPG-X2 Lua Documentation/examples/SpawningEnts.lua b/RPG-X2 Lua Documentation/examples/SpawningEnts.lua new file mode 100644 index 0000000..1e213b7 --- /dev/null +++ b/RPG-X2 Lua Documentation/examples/SpawningEnts.lua @@ -0,0 +1,7 @@ +function Example() + local ent = entity.Spawn() + if ent == nil then return; + ent:SetKeyValue("classname", "info_notnull"); + mover:SetPosition(0, 0, 0); + entity.CallSpawn(ent); +end \ No newline at end of file diff --git a/RPG-X2 Lua Documentation/examples/fusToff.lua b/RPG-X2 Lua Documentation/examples/fusToff.lua new file mode 100644 index 0000000..541ab95 --- /dev/null +++ b/RPG-X2 Lua Documentation/examples/fusToff.lua @@ -0,0 +1,10 @@ +function InitGame(levelTime, randomSeed, restart) + -- adjust the targetname + local ent = entity.Find("forcefield1"); + if ent == nil then return; + ent:SetKeyValue("classname", "func_forcefield"); + -- setting the spawnflags is optional + -- only change them if you have to + ent:SetKeyValue("spawnflags", "0") + entity.CallSpawn(ent); +end \ No newline at end of file diff --git a/RPG-X2 Lua Documentation/examples/fusToff2.lua b/RPG-X2 Lua Documentation/examples/fusToff2.lua new file mode 100644 index 0000000..ba01d63 --- /dev/null +++ b/RPG-X2 Lua Documentation/examples/fusToff2.lua @@ -0,0 +1,10 @@ +function InitGame(levelTime, randomSeed, restart) + -- adjust the model number + local ent = entity.FindBModel(22); + if ent == nil then return; + ent:SetKeyValue("classname", "func_forcefield"); + -- setting the spawnflags is optional + -- only change them if you have to + ent:SetKeyValue("spawnflags", "0") + entity.CallSpawn(ent); +end \ No newline at end of file diff --git a/STEF Game Source License.doc b/STEF Game Source License.doc new file mode 100644 index 0000000..73d2fb6 Binary files /dev/null and b/STEF Game Source License.doc differ diff --git a/StefGame Dll.sln.old b/StefGame Dll.sln.old new file mode 100644 index 0000000..c92ba20 --- /dev/null +++ b/StefGame Dll.sln.old @@ -0,0 +1,56 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BuildQVM", "BuildQVM.vcproj", "{2318F64E-DE7F-40F8-BA7F-A5952F2C1E02}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cgame", "cgame\cgame.vcproj", "{F2DB4789-20EE-4355-9844-0DEC8C2D262E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "game", "game\game.vcproj", "{A1C15954-78A8-45B5-844D-9070B2D2FB74}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ui", "ui\ui.vcproj", "{B7430F82-6210-41D4-8F07-F46B0012948F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug DLL|Win32 = Debug DLL|Win32 + Debug|Win32 = Debug|Win32 + Release DLL|Win32 = Release DLL|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2318F64E-DE7F-40F8-BA7F-A5952F2C1E02}.Debug DLL|Win32.ActiveCfg = Debug DLL|Win32 + {2318F64E-DE7F-40F8-BA7F-A5952F2C1E02}.Debug DLL|Win32.Build.0 = Debug DLL|Win32 + {2318F64E-DE7F-40F8-BA7F-A5952F2C1E02}.Debug|Win32.ActiveCfg = Debug|Win32 + {2318F64E-DE7F-40F8-BA7F-A5952F2C1E02}.Debug|Win32.Build.0 = Debug|Win32 + {2318F64E-DE7F-40F8-BA7F-A5952F2C1E02}.Release DLL|Win32.ActiveCfg = Release|Win32 + {2318F64E-DE7F-40F8-BA7F-A5952F2C1E02}.Release DLL|Win32.Build.0 = Release|Win32 + {2318F64E-DE7F-40F8-BA7F-A5952F2C1E02}.Release|Win32.ActiveCfg = Release|Win32 + {2318F64E-DE7F-40F8-BA7F-A5952F2C1E02}.Release|Win32.Build.0 = Release|Win32 + {F2DB4789-20EE-4355-9844-0DEC8C2D262E}.Debug DLL|Win32.ActiveCfg = Debug DLL|Win32 + {F2DB4789-20EE-4355-9844-0DEC8C2D262E}.Debug DLL|Win32.Build.0 = Debug DLL|Win32 + {F2DB4789-20EE-4355-9844-0DEC8C2D262E}.Debug|Win32.ActiveCfg = Debug|Win32 + {F2DB4789-20EE-4355-9844-0DEC8C2D262E}.Debug|Win32.Build.0 = Debug|Win32 + {F2DB4789-20EE-4355-9844-0DEC8C2D262E}.Release DLL|Win32.ActiveCfg = Release DLL|Win32 + {F2DB4789-20EE-4355-9844-0DEC8C2D262E}.Release DLL|Win32.Build.0 = Release DLL|Win32 + {F2DB4789-20EE-4355-9844-0DEC8C2D262E}.Release|Win32.ActiveCfg = Release|Win32 + {F2DB4789-20EE-4355-9844-0DEC8C2D262E}.Release|Win32.Build.0 = Release|Win32 + {A1C15954-78A8-45B5-844D-9070B2D2FB74}.Debug DLL|Win32.ActiveCfg = Debug DLL|Win32 + {A1C15954-78A8-45B5-844D-9070B2D2FB74}.Debug DLL|Win32.Build.0 = Debug DLL|Win32 + {A1C15954-78A8-45B5-844D-9070B2D2FB74}.Debug|Win32.ActiveCfg = Debug|Win32 + {A1C15954-78A8-45B5-844D-9070B2D2FB74}.Debug|Win32.Build.0 = Debug|Win32 + {A1C15954-78A8-45B5-844D-9070B2D2FB74}.Release DLL|Win32.ActiveCfg = Release DLL|Win32 + {A1C15954-78A8-45B5-844D-9070B2D2FB74}.Release DLL|Win32.Build.0 = Release DLL|Win32 + {A1C15954-78A8-45B5-844D-9070B2D2FB74}.Release|Win32.ActiveCfg = Release|Win32 + {A1C15954-78A8-45B5-844D-9070B2D2FB74}.Release|Win32.Build.0 = Release|Win32 + {B7430F82-6210-41D4-8F07-F46B0012948F}.Debug DLL|Win32.ActiveCfg = Debug DLL|Win32 + {B7430F82-6210-41D4-8F07-F46B0012948F}.Debug DLL|Win32.Build.0 = Debug DLL|Win32 + {B7430F82-6210-41D4-8F07-F46B0012948F}.Debug|Win32.ActiveCfg = Debug|Win32 + {B7430F82-6210-41D4-8F07-F46B0012948F}.Debug|Win32.Build.0 = Debug|Win32 + {B7430F82-6210-41D4-8F07-F46B0012948F}.Release DLL|Win32.ActiveCfg = Release DLL|Win32 + {B7430F82-6210-41D4-8F07-F46B0012948F}.Release DLL|Win32.Build.0 = Release DLL|Win32 + {B7430F82-6210-41D4-8F07-F46B0012948F}.Release|Win32.ActiveCfg = Release|Win32 + {B7430F82-6210-41D4-8F07-F46B0012948F}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/buildqvm.dsw b/buildqvm.dsw new file mode 100644 index 0000000..db65b07 --- /dev/null +++ b/buildqvm.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "BuildQVM"=".\buildqvm.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/buildqvm.opt b/buildqvm.opt new file mode 100644 index 0000000..2527713 Binary files /dev/null and b/buildqvm.opt differ diff --git a/buildvms.bat b/buildvms.bat new file mode 100644 index 0000000..0815038 --- /dev/null +++ b/buildvms.bat @@ -0,0 +1,14 @@ +@echo off +echo. +cd game +pause +if exist game.bat call game +pause +cd ..\cgame +if exist cgame.bat call cgame +pause +cd ..\ui +if exist ui.bat call ui +pause +cd .. +echo Finished. diff --git a/cg_view_bak.c b/cg_view_bak.c new file mode 100644 index 0000000..5f4123c --- /dev/null +++ b/cg_view_bak.c @@ -0,0 +1,1066 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_view.c -- setup all the parameters (position, angle, etc) +// for a 3D rendering +#include "cg_local.h" +#include "cg_screenfx.h" + +#define MAX_SHAKE_INTENSITY 16.0f + +#define FRAMES_DOOR 16 + +/* +============================================================================= + + MODEL TESTING + +The viewthing and gun positioning tools from Q2 have been integrated and +enhanced into a single model testing facility. + +Model viewing can begin with either "testmodel " or "testgun ". + +The names must be the full pathname after the basedir, like +"models/weapons/v_launch/tris.md3" or "players/male/tris.md3" + +Testmodel will create a fake entity 100 units in front of the current view +position, directly facing the viewer. It will remain immobile, so you can +move around it to view it from different angles. + +Testgun will cause the model to follow the player around and supress the real +view weapon model. The default frame 0 of most guns is completely off screen, +so you will probably have to cycle a couple frames to see it. + +"nextframe", "prevframe", "nextskin", and "prevskin" commands will change the +frame or skin of the testmodel. These are bound to F5, F6, F7, and F8 in +q3default.cfg. + +If a gun is being tested, the "gun_x", "gun_y", and "gun_z" variables will let +you adjust the positioning. + +Note that none of the model testing features update while the game is paused, so +it may be convenient to test with deathmatch set to 1 so that bringing down the +console doesn't pause the game. + +============================================================================= +*/ + +/* +================= +CG_TestModel_f + +Creates an entity in front of the current position, which +can then be moved around +================= +*/ +void CG_TestModel_f (void) { + vec3_t angles; + + memset( &cg.testModelEntity, 0, sizeof(cg.testModelEntity) ); + if ( trap_Argc() < 2 ) { + return; + } + + Q_strncpyz (cg.testModelName, CG_Argv( 1 ), MAX_QPATH ); + cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); + + if ( trap_Argc() == 3 ) { + cg.testModelEntity.backlerp = atof( CG_Argv( 2 ) ); + cg.testModelEntity.frame = 1; + cg.testModelEntity.oldframe = 0; + } + if (! cg.testModelEntity.hModel ) { + CG_Printf( "Can't register model\n" ); + return; + } + + VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[0], cg.testModelEntity.origin ); + + angles[PITCH] = 0; + angles[YAW] = 180 + cg.refdefViewAngles[1]; + angles[ROLL] = 0; + + AnglesToAxis( angles, cg.testModelEntity.axis ); + cg.testGun = qfalse; +} + +/* +================= +CG_TestGun_f + +Replaces the current view weapon with the given model +================= +*/ +void CG_TestGun_f (void) { + CG_TestModel_f(); + cg.testGun = qtrue; + cg.testModelEntity.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON; +} + + +void CG_TestModelNextFrame_f (void) { + cg.testModelEntity.frame++; + CG_Printf( "frame %i\n", cg.testModelEntity.frame ); +} + +void CG_TestModelPrevFrame_f (void) { + cg.testModelEntity.frame--; + if ( cg.testModelEntity.frame < 0 ) { + cg.testModelEntity.frame = 0; + } + CG_Printf( "frame %i\n", cg.testModelEntity.frame ); +} + +void CG_TestModelNextSkin_f (void) { + cg.testModelEntity.skinNum++; + CG_Printf( "skin %i\n", cg.testModelEntity.skinNum ); +} + +void CG_TestModelPrevSkin_f (void) { + cg.testModelEntity.skinNum--; + if ( cg.testModelEntity.skinNum < 0 ) { + cg.testModelEntity.skinNum = 0; + } + CG_Printf( "skin %i\n", cg.testModelEntity.skinNum ); +} + +static void CG_AddTestModel (void) { + int i; + + // re-register the model, because the level may have changed + cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); + if (! cg.testModelEntity.hModel ) { + CG_Printf ("Can't register model\n"); + return; + } + + // if testing a gun, set the origin reletive to the view origin + if ( cg.testGun ) { + VectorCopy( cg.refdef.vieworg, cg.testModelEntity.origin ); + VectorCopy( cg.refdef.viewaxis[0], cg.testModelEntity.axis[0] ); + VectorCopy( cg.refdef.viewaxis[1], cg.testModelEntity.axis[1] ); + VectorCopy( cg.refdef.viewaxis[2], cg.testModelEntity.axis[2] ); + + // allow the position to be adjusted + for (i=0 ; i<3 ; i++) { + cg.testModelEntity.origin[i] += cg.refdef.viewaxis[0][i] * cg_gun_x.value; + cg.testModelEntity.origin[i] += cg.refdef.viewaxis[1][i] * cg_gun_y.value; + cg.testModelEntity.origin[i] += cg.refdef.viewaxis[2][i] * cg_gun_z.value; + } + } + + trap_R_AddRefEntityToScene( &cg.testModelEntity ); +} + + + +//============================================================================ + + +/* +================= +CG_CalcVrect + +Sets the coordinates of the rendered window +================= +*/ +static void CG_CalcVrect (void) { + int size; + + // the intermission should allways be full screen + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { + size = 100; + } else { + // bound normal viewsize + if (cg_viewsize.integer < 30) { + trap_Cvar_Set ("cg_viewsize","30"); + size = 30; + } else if (cg_viewsize.integer > 100) { + trap_Cvar_Set ("cg_viewsize","100"); + size = 100; + } else { + size = cg_viewsize.integer; + } + + } + cg.refdef.width = cgs.glconfig.vidWidth*size/100; + cg.refdef.width &= ~1; + + cg.refdef.height = cgs.glconfig.vidHeight*size/100; + cg.refdef.height &= ~1; + + cg.refdef.x = (cgs.glconfig.vidWidth - cg.refdef.width)/2; + cg.refdef.y = (cgs.glconfig.vidHeight - cg.refdef.height)/2; +} + +//============================================================================== + + +/* +=============== +CG_OffsetThirdPersonView + +=============== +*/ +#define FOCUS_DISTANCE 512 +static void CG_OffsetThirdPersonView( void ) { + vec3_t forward, right, up; + vec3_t view; + vec3_t focusAngles; + trace_t trace; + static vec3_t mins = { -4, -4, -4 }; + static vec3_t maxs = { 4, 4, 4 }; + vec3_t focusPoint; + float focusDist; + float forwardScale, sideScale; + char medicrevive[32]; + int medicrevive_int; + vec3_t camPlayerPos; //TiM + + + cg.refdef.vieworg[2] += cg.predictedPlayerState.viewheight; + + VectorCopy( cg.refdefViewAngles, focusAngles ); + VectorCopy( cg.refdef.vieworg, camPlayerPos); //Copy the values b4 we offset; + + //RPG-X: TiM - Incorporated offsets so third person can be more dynamic + //Woo! I figured out how AngleVectors and VectorMA work!! ^_^ + AngleVectors( cg.refdefViewAngles, NULL, right, NULL); + VectorMA( cg.refdef.vieworg, cg_thirdPersonHorzOffset.value, right, cg.refdef.vieworg ); + //cg.refdef.vieworg[0] += cg_thirdPersonHorzOffset.value; + cg.refdef.vieworg[2] += cg_thirdPersonVertOffset.value; + + // if dead, look at killer + //RPG-X: Fix camera movment when play dies with medics revive turned on + trap_Cvar_VariableStringBuffer( "rpg_medicsrevive", medicrevive, 32 ); + medicrevive_int = atoi(medicrevive); + + //TiM: Meh, you don't spin around to look at your killer in real life. O_o + //Plus, this screws up the model system :( + /*if(medicrevive_int == 1){ + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 1 ) { + + focusAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; + cg.refdefViewAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; + } + }else{ + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + focusAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; + cg.refdefViewAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; + } + }*/ + + if ( focusAngles[PITCH] > 89 ) + { + focusAngles[PITCH] = 89; // don't go too far overhead + } + else if ( focusAngles[PITCH] < -89 ) + { + focusAngles[PITCH] = -89; + } + + AngleVectors( focusAngles, forward, NULL, NULL ); + + VectorMA( cg.refdef.vieworg, FOCUS_DISTANCE, forward, focusPoint ); + + VectorCopy( cg.refdef.vieworg, view ); + + view[2] += 16; + + cg.refdefViewAngles[PITCH] *= 0.5; + + AngleVectors( cg.refdefViewAngles, forward, right, up ); + + forwardScale = cos( cg_thirdPersonAngle.value / 180 * M_PI ); + sideScale = sin( cg_thirdPersonAngle.value / 180 * M_PI ); + VectorMA( view, -cg_thirdPersonRange.value * forwardScale, forward, view ); + VectorMA( view, -cg_thirdPersonRange.value * sideScale, right, view ); + + // trace a ray from the origin to the viewpoint to make sure the view isn't + // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything + + //TiM : Sometimes if the value of these variables is set to extreme numbers, they'll go thru walls. O_o + //This trace function is to fix that. + //If player is using these CVARs... + if ( cg_thirdPersonVertOffset.value != 0 || cg_thirdPersonHorzOffset.value != 0) { + //Do a trace from playermodel's head to our view location + CG_Trace( &trace, camPlayerPos, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); + + //Okay, the trace hit something... O_o + if ( trace.fraction != 1.0 ) { + //copy where it hit to our view origin. :) + VectorCopy( trace.endpos, cg.refdef.vieworg ); + } + } + + CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); + + if ( trace.fraction != 1.0 ) { + VectorCopy( trace.endpos, view ); + view[2] += (1.0 - trace.fraction) * 32; + // try another trace to this position, because a tunnel may have the ceiling + // close enogh that this is poking out + + CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); + VectorCopy( trace.endpos, view ); + } + + VectorCopy( view, cg.refdef.vieworg ); + + // select pitch to look at focus point from vieword + VectorSubtract( focusPoint, cg.refdef.vieworg, focusPoint ); + focusDist = sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] ); + if ( focusDist < 1 ) { + focusDist = 1; // should never happen + } + cg.refdefViewAngles[PITCH] = -180 / M_PI * atan2( focusPoint[2], focusDist ); + cg.refdefViewAngles[YAW] -= cg_thirdPersonAngle.value; +} + + +// this causes a compiler bug on mac MrC compiler +static void CG_StepOffset( void ) { + int timeDelta; + + // smooth out stair climbing + timeDelta = cg.time - cg.stepTime; + 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; + char medicrevive[32]; + int medicrevive_int; + + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { + return; + } + + origin = cg.refdef.vieworg; + angles = cg.refdefViewAngles; + + // if dead, fix the angle and don't add any kick + //RPG-X: Fix camera movment when play dies with medics revive turned on + trap_Cvar_VariableStringBuffer( "rpg_medicsrevive", medicrevive, 32 ); + medicrevive_int = atoi(medicrevive); + if(medicrevive_int == 1){ + if ( cg.snap->ps.stats[STAT_HEALTH] <= 1 ) { + angles[ROLL] = 40; + angles[PITCH] = -15; + angles[YAW] = cg.snap->ps.stats[STAT_DEAD_YAW]; + origin[2] += cg.predictedPlayerState.viewheight; + return; + } + }else{ + if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) { + angles[ROLL] = 40; + angles[PITCH] = -15; + angles[YAW] = cg.snap->ps.stats[STAT_DEAD_YAW]; + origin[2] += cg.predictedPlayerState.viewheight; + return; + } + } + + // add angles based on weapon kick + VectorAdd (angles, cg.kick_angles, angles); + + // add angles based on damage kick + if ( cg.damageTime ) { + ratio = cg.time - cg.damageTime; + if ( ratio < DAMAGE_DEFLECT_TIME ) { + ratio /= DAMAGE_DEFLECT_TIME; + angles[PITCH] += ratio * cg.v_dmg_pitch; + angles[ROLL] += ratio * cg.v_dmg_roll; + } else { + ratio = 1.0 - ( ratio - DAMAGE_DEFLECT_TIME ) / DAMAGE_RETURN_TIME; + if ( ratio > 0 ) { + angles[PITCH] += ratio * cg.v_dmg_pitch; + angles[ROLL] += ratio * cg.v_dmg_roll; + } + } + } + + // add pitch based on fall kick +#if 0 + ratio = ( cg.time - cg.landTime) / FALL_TIME; + if (ratio < 0) + ratio = 0; + angles[PITCH] += ratio * cg.fall_value; +#endif + + // add angles based on velocity + VectorCopy( cg.predictedPlayerState.velocity, predictedVelocity ); + + delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[0]); + angles[PITCH] += delta * cg_runpitch.value; + + delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[1]); + angles[ROLL] -= delta * cg_runroll.value; + + // add angles based on bob + + // make sure the bob is visible even at low speeds + speed = cg.xyspeed > 200 ? cg.xyspeed : 200; + + delta = cg.bobfracsin * cg_bobpitch.value * speed; + if (cg.predictedPlayerState.pm_flags & PMF_DUCKED) + delta *= 3; // crouching + angles[PITCH] += delta; + delta = cg.bobfracsin * cg_bobroll.value * speed; + if (cg.predictedPlayerState.pm_flags & PMF_DUCKED) + delta *= 3; // crouching accentuates roll + if (cg.bobcycle & 1) + delta = -delta; + angles[ROLL] += delta; + +//=================================== + + // add view height + origin[2] += cg.predictedPlayerState.viewheight; + + // smooth out duck height changes + timeDelta = cg.time - cg.duckTime; + if ( timeDelta < 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 + + VectorAdd (origin, cg.kick_origin, origin); + + // pivot the eye based on a neck length +#if 0 + { +#define NECK_LENGTH 8 + vec3_t forward, up; + + cg.refdef.vieworg[2] -= NECK_LENGTH; + AngleVectors( cg.refdefViewAngles, forward, NULL, up ); + VectorMA( cg.refdef.vieworg, 3, forward, cg.refdef.vieworg ); + VectorMA( cg.refdef.vieworg, NECK_LENGTH, up, cg.refdef.vieworg ); + } +#endif +} + + +/* +------------------------- +CGCam_Shake +------------------------- +*/ + +void CG_CameraShake( float intensity, int duration ) +{ + if ( intensity > MAX_SHAKE_INTENSITY ) + intensity = MAX_SHAKE_INTENSITY; + + cg.shake_intensity = intensity; + cg.shake_duration = duration; + cg.shake_start = cg.time; +} + + + + +/* +------------------------- +CG_UpdateShake + +This doesn't actually affect the camera's info, but passed information instead +------------------------- +*/ + +void CG_UpdateCameraShake( vec3_t origin, vec3_t angles ) +{ + vec3_t moveDir; + float intensity_scale, intensity; + int i; + + if ( cg.shake_duration <= 0 ) + return; + + if ( cg.time > ( cg.shake_start + cg.shake_duration ) ) + { + cg.shake_intensity = 0; + cg.shake_duration = 0; + cg.shake_start = 0; + return; + } + + //intensity_scale now also takes into account FOV with 90.0 as normal + intensity_scale = 1.0f - ( (float) ( cg.time - cg.shake_start ) / (float) cg.shake_duration ) * (cg.refdef.fov_x/90.0f); + + intensity = cg.shake_intensity * intensity_scale; + + for (i=0; i < 3; i++ ) + moveDir[i] = ( crandom() * intensity ); + + //FIXME: Lerp + + //Move the camera + VectorAdd( origin, moveDir, origin ); + + for ( i=0; i < 3; i++ ) + moveDir[i] = ( crandom() * intensity ); + + //FIXME: Lerp + + //Move the angles + VectorAdd( angles, moveDir, angles ); +} + + +//====================================================================== + +void CG_ZoomDown_f( void ) +{ + if ( cg.snap->ps.persistant[PERS_CLASS] == PC_NOCLASS + || cg.snap->ps.persistant[PERS_CLASS] != PC_SECURITY + && cg.snap->ps.persistant[PERS_CLASS] != PC_ALPHAOMEGA22 + && cg.snap->ps.persistant[PERS_CLASS] != PC_ADMIN ) + {//in a class-based game, only these can zoom + cg.zoomed = qfalse; + cg.zoomLocked = qfalse; + return; + } + + // The zoom hasn't been started yet, so do it now + if ( !cg.zoomed ) + { + cg.zoomLocked = qfalse; + cg.zoomed = qtrue; + cg_zoomFov.value = cg_fov.value; + cg.zoomTime = cg.time; + trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.zoomStart ); + return; + } + + // Can only snap out of the zoom mode if it has already been locked (CG_ZoomUp_f has been called) + if ( cg.zoomLocked ) + { + // Snap out of zoom mode + cg.zoomed = qfalse; + cg.zoomTime = cg.time; + trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.zoomEnd ); + } +} + +void CG_ZoomUp_f( void ) +{ + + if ( cg.zoomed ) { + // Freeze the zoom mode + cg.zoomLocked = qtrue; + } +} + +/* +==================== +CG_CalcFov + +Fixed fov at intermissions, otherwise account for fov variable and zooms. +==================== +*/ +#define WAVE_AMPLITUDE 1 +#define WAVE_FREQUENCY 0.4 + +#define FOV_MAX 120 + +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 = 80; + } else { + fov_x = cg_fov.value; + if ( fov_x < 1 ) { + fov_x = 1; + } else if ( fov_x > FOV_MAX ) { + fov_x = FOV_MAX; + } + } + + // account for zooms + zoomFov = cg_zoomFov.value; + if ( zoomFov < 1 ) { + zoomFov = 1; + } else if ( zoomFov > FOV_MAX) { + zoomFov = FOV_MAX; + } + + // Disable zooming when in third person + if ( cg.zoomed && !cg.renderingThirdPerson ) + { + if ( !cg.zoomLocked ) + { + // Interpolate current zoom level + cg_zoomFov.value = cg_fov.value - ((float)( cg.time - cg.zoomTime ) / ZOOM_IN_TIME + ZOOM_START_PERCENT) + * ( cg_fov.value - MAX_ZOOM_FOV ); + + // Clamp zoomFov + if ( cg_zoomFov.value < MAX_ZOOM_FOV ) + { + cg_zoomFov.value = MAX_ZOOM_FOV; + } + else if ( cg_zoomFov.value > cg_fov.value ) + { + cg_zoomFov.value = cg_fov.value; + } + else + {//still zooming + static zoomSoundTime = 0; + + if ( zoomSoundTime < cg.time ) + { + trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_LOCAL, cgs.media.zoomLoop ); + zoomSoundTime = cg.time + 300; + } + } + } + + fov_x = cg_zoomFov.value; + } else { + f = ( cg.time - cg.zoomTime ) / (float)ZOOM_OUT_TIME; + if ( f > 1.0 ) { + fov_x = fov_x; + } else { + fov_x = zoomFov + f * ( fov_x - zoomFov ); + } + } + } + + if (cg.predictedPlayerState.introTime > cg.time) + { // The stuff is "holodecking in". + fov_x = 80; + } + + + x = cg.refdef.width / tan( fov_x / 360 * M_PI ); + fov_y = atan2( cg.refdef.height, x ); + fov_y = fov_y * 360 / M_PI; + + // warp if underwater + contents = CG_PointContents( cg.refdef.vieworg, -1 ); + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ){ + phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; + v = WAVE_AMPLITUDE * sin( phase ); + fov_x += v; + fov_y -= v; + inwater = qtrue; + } + else { + inwater = qfalse; + } + + + // set it + cg.refdef.fov_x = fov_x; + cg.refdef.fov_y = fov_y; + + if ( !cg.zoomed ) { + cg.zoomSensitivity = 1; + } else { + cg.zoomSensitivity = cg.refdef.fov_y / 75.0; + } + + return inwater; +} + + + + +/* +=============== +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; + + // intermission view + if ( ps->pm_type == PM_INTERMISSION ) { + VectorCopy( ps->origin, cg.refdef.vieworg ); + VectorCopy( ps->viewangles, cg.refdefViewAngles ); + AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); + return CG_CalcFov(); + } + + cg.bobcycle = ( ps->bobCycle & 128 ) >> 7; + cg.bobfracsin = fabs( sin( ( ps->bobCycle & 127 ) / 127.0 * M_PI ) ); + cg.xyspeed = sqrt( ps->velocity[0] * ps->velocity[0] + + ps->velocity[1] * ps->velocity[1] ); + + + VectorCopy( ps->origin, cg.refdef.vieworg ); + VectorCopy( ps->viewangles, cg.refdefViewAngles ); + + // add error decay + if ( cg_errorDecay.value > 0 ) { + int t; + float f; + + t = cg.time - cg.predictedErrorTime; + f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; + if ( f > 0 && f < 1 ) { + VectorMA( cg.refdef.vieworg, f, cg.predictedError, cg.refdef.vieworg ); + } else { + cg.predictedErrorTime = 0; + } + } + + if ( cg.renderingThirdPerson ) { + // back away from character + CG_OffsetThirdPersonView(); + } else { + // offset for local bobbing and kicks + CG_OffsetFirstPersonView(); + } + + // shake the camera if necessary + CG_UpdateCameraShake( cg.refdef.vieworg, cg.refdefViewAngles ); + // position eye reletive to origin + AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); + + if ( cg.hyperspace ) { + cg.refdef.rdflags |= RDF_NOWORLDMODEL | RDF_HYPERSPACE; + } + + // field of view + return CG_CalcFov(); +} + + +/* +===================== +CG_PowerupTimerSounds +===================== +*/ +static void CG_PowerupTimerSounds( void ) { + int i; + int t; + + // powerup timers going away + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + t = cg.snap->ps.powerups[i]; + + // kef -- hack hack hack. additionally, hack. + if ( (PW_OUCH == i) || (PW_GHOST == i)) + { + continue; + } + if ( t <= cg.time ) { + continue; + } + if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { + continue; + } + if ( ( t - cg.time ) / POWERUP_BLINK_TIME != ( t - cg.oldTime ) / POWERUP_BLINK_TIME ) { + trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_ITEM, cgs.media.wearOffSound ); + } + } +} + + + +//========================================================================= + +/* +============= +CG_IntroModel + +This is when the player is starting the level. +============= +*/ +void CG_AddIntroModel(playerState_t *ps, int time) +{ + static int soundpoint=0, lasttime=999999; + refEntity_t doorbox; + float alpha; + byte a; + //char pClass[MAX_QPATH]; + //char pRank[MAX_QPATH]; + + if (lasttime > time) + { // Restart everything. + soundpoint=0; + } + + lasttime=time; + + // add the model + memset( &doorbox, 0, sizeof( doorbox ) ); + VectorCopy( cg.refdef.vieworg, doorbox.lightingOrigin ); + + doorbox.shaderRGBA[0] = 255; + doorbox.shaderRGBA[1] = 255; + doorbox.shaderRGBA[2] = 255; + doorbox.shaderRGBA[3] = 255; + + doorbox.hModel = cgs.media.doorbox; + if (!doorbox.hModel) { + return; + } + + + VectorMA(cg.refdef.vieworg, 25, cg.refdef.viewaxis[0], doorbox.origin); + VectorMA(doorbox.origin, -35, cg.refdef.viewaxis[2], doorbox.origin); + AnglesToAxis(cg.refdefViewAngles, doorbox.axis); + + VectorScale(doorbox.axis[0], -1.0, doorbox.axis[0]); + VectorScale(doorbox.axis[1], -1.0, doorbox.axis[1]); + + if (soundpoint <= 0) + { // First part... "Prepare to compete." + if (time >= TIME_INIT) + { + soundpoint = 1; + trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.holoInitSound ); + } + doorbox.frame = 0; + } + else if (soundpoint == 1) + { // Second part... Open door after "prepare". + if (time >= TIME_DOOR_START) + { + soundpoint = 2; + trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.holoDoorSound ); + } + doorbox.frame = 0; + } + else if (soundpoint == 2) + { // Third part... Fade in after opening door. + if (time >= TIME_FADE_START) + { + soundpoint = 3; + trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.holoFadeSound ); + doorbox.frame = FRAMES_DOOR-1; + } + else + { + doorbox.frame = ((float)(time - TIME_DOOR_START) / 100.0) + 1; + if (doorbox.frame >= FRAMES_DOOR) + { + doorbox.frame=FRAMES_DOOR-1; + } + else + { + doorbox.oldframe = doorbox.frame-1; + doorbox.backlerp = (float)(doorbox.frame) - ((float)(time - TIME_DOOR_START) / 100.0); + } + } + } + else + { // Final part... Fade out the model. + + alpha = 1.0 - ((float)(time - TIME_FADE_START) / (float)TIME_FADE_DUR); + + if (alpha<0.0) + { + alpha=0.0; + } + + a=255.0*alpha; + if (a<=0) + { // An alpha of zero defaults to opaque... Makes sense, why even send something that is 100% transparent? + a=1; + +/* trap_Cvar_VariableStringBuffer( "ui_playerclass", pClass, sizeof(pClass) ); + trap_Cvar_VariableStringBuffer( "ui_playerrank", pRank, sizeof(pRank) ); + + if ( !strcmp( pClass, "maker" ) || !strcmp( pClass, "alphaomega22" ) ) { + trap_SendClientCommand( "class command" ); + trap_SendClientCommand( va( "rank %s", pRank) ); + } + + trap_SendClientCommand( va( "class %s", pClass) ); + trap_SendClientCommand( va( "rank %s", pRank) );*/ + } + + doorbox.shaderRGBA[0] = 255; + doorbox.shaderRGBA[1] = 255; + doorbox.shaderRGBA[2] = 255; + doorbox.shaderRGBA[3] = a; + doorbox.frame = FRAMES_DOOR-1; + } + + doorbox.renderfx |= (RF_DEPTHHACK|RF_FORCE_ENT_ALPHA|RF_FULLBRIGHT); + + trap_R_AddRefEntityToScene(&doorbox); +} + + +//========================================================================= + +/* +================= +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; + } + + // any looped sounds will be respecified as entities + // are added to the render list + trap_S_ClearLoopingSounds(); + + // 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; + } + + // let the client system know what our weapon and zoom settings are + trap_SetUserCmdValue( cg.weaponSelect, cg.zoomSensitivity ); + + // this counter will be bumped for every valid scene we generate + cg.clientFrame++; + + // update cg.predictedPlayerState + CG_PredictPlayerState(); + + // decide on third person view + cg.renderingThirdPerson = cg_thirdPerson.integer || (cg.snap->ps.stats[STAT_HEALTH] <= 0); + + // build cg.refdef + inwater = CG_CalcViewValues(); + + // first person blend blobs, done after AnglesToAxis + if ( !cg.renderingThirdPerson ) + { + CG_DrawFullScreenFX(); + } + + + // build the render lists + if ( !cg.hyperspace ) { + CG_AddPacketEntities(); // adter calcViewValues, so predicted player state is correct + CG_AddMarks(); + CG_AddLocalEntities(); + } + + if (cg.predictedPlayerState.introTime > cg.time) + { // Render the holodeck doors + CG_AddIntroModel(&cg.predictedPlayerState, TIME_INTRO - (cg.predictedPlayerState.introTime - cg.time)); + } + + CG_AddViewWeapon( &cg.predictedPlayerState ); + + // 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 + trap_S_Respatialize( cg.snap->ps.clientNum, cg.refdef.vieworg, cg.refdef.viewaxis, inwater ); + + // warning sounds when powerup is wearing off + CG_PowerupTimerSounds(); + + // make sure the lagometerSample and frame timing isn't done twice when in stereo + if ( stereoView != STEREO_RIGHT ) { + cg.frametime = cg.time - cg.oldTime; + if ( cg.frametime < 0 ) { + cg.frametime = 0; + } + cg.oldTime = cg.time; + CG_AddLagometerFrameInfo(); + } + + // actually issue the rendering calls + CG_DrawActive( stereoView ); + + if ( cg_stats.integer ) { + CG_Printf( "cg.clientFrame:%i\n", cg.clientFrame ); + } +} + diff --git a/cgame/Makefile b/cgame/Makefile new file mode 100644 index 0000000..24071ac --- /dev/null +++ b/cgame/Makefile @@ -0,0 +1,134 @@ +default: so +so: build_so + +# compiler to use for building shared objects +CC = gcc + +# determine arch and platform +ARCH=$(shell uname -m | sed -e s/i.86/i386/) +PLATFORM=$(shell uname|sed -e s/_.*//|tr '[:upper:]' '[:lower:]') + +# cflags for the compiler +ifeq ($(PLATFORM), mingw32) +SOCFLAGS = $(CFLAGS) +else +SOCFLAGS = $(CFLAGS) -fPIC +endif + +# set extension +ifeq ($(PLATFORM), mingw32) +EXT=dll +ARCH=x86 +else +EXT=so +endif + +# cgame objects +OBJ = \ + fx_transporter.o \ + fx_tetrion.o \ + fx_stasis.o \ + fx_scavenger.o \ + fx_quantum.o \ + fx_phaser.o \ + fx_misc.o \ + fx_lib.o \ + fx_item.o \ + fx_imod.o \ + fx_grenade.o \ + fx_dreadnought.o \ + fx_compression.o \ + fx_borg.o \ + cg_weapons.o \ + cg_view.o \ + cg_snapshot.o \ + cg_servercmds.o \ + cg_screenfx.o \ + cg_scoreboard.o \ + cg_predict.o \ + cg_playerstate.o \ + cg_players.o \ + cg_marks.o \ + cg_main.o \ + cg_localents.o \ + cg_info.o \ + cg_event.o \ + cg_env.o \ + cg_ents.o \ + cg_effects.o \ + cg_drawtools.o \ + cg_draw.o \ + cg_consolecmds.o + +# depency objects from game +OBJDEP = \ + q_shared.o \ + q_math.o \ + bg_misc.o \ + bg_pmove.o \ + bg_slidemove.o + + # object for syscalls to the engine +SOOBJ = \ + cg_syscalls.o + +# do cc for shared library +DO_SOCC = $(CC) $(SOCFLAGS) -o $@ -c $< + +build_so: DO_CC=$(DO_SOCC) + +# cgame +cg_consolecmds.o : cg_consolecmds.c; $(DO_CC) +cg_draw.o : cg_draw.c; $(DO_CC) +cg_drawtools.o : cg_drawtools.c; $(DO_CC) +cg_effects.o : cg_effects.c; $(DO_CC) +cg_ents.o : cg_ents.c; $(DO_CC) +cg_env.o : cg_env.c; $(DO_CC) +cg_event.o : cg_event.c; $(DO_CC) +cg_info.o : cg_info.c; $(DO_CC) +cg_localents.o : cg_localents.c; $(DO_CC) +cg_main.o : cg_main.c; $(DO_CC) +cg_marks.o : cg_marks.c; $(DO_CC) +cg_players.o : cg_players.c; $(DO_CC) +cg_playerstate.o : cg_playerstate.c; $(DO_CC) +cg_predict.o : cg_predict.c; $(DO_CC) +cg_scoreboard.o : cg_scoreboard.c; $(DO_CC) +cg_screenfx.o : cg_screenfx.c; $(DO_CC) +cg_servercmds.o : cg_servercmds.c; $(DO_CC) +cg_snapshot.o : cg_snapshot.c; $(DO_CC) +cg_view.o : cg_view.c; $(DO_CC) +cg_weapons.o : cg_weapons.c; $(DO_CC) +fx_borg.o : fx_borg.c; $(DO_CC) +fx_compression.o : fx_compression.c; $(DO_CC) +fx_dreadnought.o : fx_dreadnought.c; $(DO_CC) +fx_grenade.o : fx_grenade.c; $(DO_CC) +fx_imod.o : fx_imod.c; $(DO_CC) +fx_item.o : fx_item.c; $(DO_CC) +fx_lib.o : fx_lib.c; $(DO_CC) +fx_misc.o : fx_misc.c; $(DO_CC) +fx_phaser.o : fx_phaser.c; $(DO_CC) +fx_quantum.o : fx_quantum.c; $(DO_CC) +fx_scavenger.o : fx_scavenger.c; $(DO_CC) +fx_stasis.o : fx_stasis.c; $(DO_CC) +fx_tetrion.o : fx_tetrion.c; $(DO_CC) +fx_transporter.o : fx_transporter.c; $(DO_CC) + +# dependencies from game +q_shared.o: ../game/q_shared.c; $(DO_CC) +q_math.o: ../game/q_math.c; $(DO_CC) +bg_misc.o: ../game/bg_misc.c; $(DO_CC) +bg_pmove.o: ../game/bg_pmove.c; $(DO_CC) +bg_slidemove.o: ../game/bg_slidemove.c; $(DO_CC) + +# cgame syscalls +cg_syscalls.o : cg_syscalls.c; $(DO_CC) + +build_so: $(OBJDEP) $(OBJ) $(SOOBJ) +ifeq ($(PLATFORM), mingw32) + $(CC) -shared -Wl,--export-all-symbols,-soname,cgame$(ARCH).$(EXT) -o cgame$(ARCH).$(EXT) $(OBJ) $(OBJDEP) $(SOOBJ) +else + $(CC) -shared -Wl,--export-dynamic,-soname,cgame$(ARCH).$(EXT) -o cgame$(ARCH).$(EXT) $(OBJ) $(OBJDEP) $(SOOBJ) +endif + +clean: + rm -f *.o *.$(EXT) diff --git a/cgame/cg_anims.h b/cgame/cg_anims.h new file mode 100644 index 0000000..9f5422b --- /dev/null +++ b/cgame/cg_anims.h @@ -0,0 +1,1798 @@ +/* IMPORTED FROM SINGLE PLAYER BY RPG-X J2J */ + +//Quiet the linker problems +#ifndef stringtableforanims +#define stringtableforanims + +//cg_players_c + +#include "../game/q_shared.h" +//#include "../game/bg_public.h" + +//This has to wait till the exe source :-( +/* +//This is New and shiny from single player +typedef enum //# animNumber_e +{ + //================================================= + //ANIMS IN WHICH UPPER AND LOWER OBJECTS ARE IN MD3 + //================================================= + //# #sep BOTH_ DEATHS + BOTH_DEATH1 = 0, //# First Death anim + BOTH_DEATH2, //# Second Death anim + BOTH_DEATH3, //# Third Death anim + BOTH_DEATH4, //# Fourth Death anim + BOTH_DEATH5, //# Fifth Death anim + BOTH_DEATH6, //# Sixth Death anim + BOTH_DEATH7, //# Seventh Death anim + + BOTH_DEATHFORWARD1, //# First Death in which they get thrown forward + BOTH_DEATHFORWARD2, //# Second Death in which they get thrown forward + BOTH_DEATHBACKWARD1, //# First Death in which they get thrown backward + BOTH_DEATHBACKWARD2, //# Second Death in which they get thrown backward + + BOTH_DEATH1IDLE, //# Idle while close to death + BOTH_LYINGDEATH1, //# Death to play when killed lying down + BOTH_STUMBLEDEATH1, //# Stumble forward and fall face first death + BOTH_FALLDEATH1, //# Fall forward off a high cliff and splat death - start + BOTH_FALLDEATH1INAIR, //# Fall forward off a high cliff and splat death - loop + BOTH_FALLDEATH1LAND, //# Fall forward off a high cliff and splat death - hit bottom + //# #sep BOTH_ DEAD POSES # Should be last frame of corresponding previous anims + BOTH_DEAD1, //# First Death finished pose + BOTH_DEAD2, //# Second Death finished pose + BOTH_DEAD3, //# Third Death finished pose + BOTH_DEAD4, //# Fourth Death finished pose + BOTH_DEAD5, //# Fifth Death finished pose + BOTH_DEAD6, //# Sixth Death finished pose + BOTH_DEAD7, //# Seventh Death finished pose + BOTH_DEADFORWARD1, //# First thrown forward death finished pose + BOTH_DEADFORWARD2, //# Second thrown forward death finished pose + BOTH_DEADBACKWARD1, //# First thrown backward death finished pose + BOTH_DEADBACKWARD2, //# Second thrown backward death finished pose + BOTH_LYINGDEAD1, //# Killed lying down death finished pose + BOTH_STUMBLEDEAD1, //# Stumble forward death finished pose + BOTH_FALLDEAD1LAND, //# Fall forward and splat death finished pose + //# #sep BOTH_ DEAD TWITCH/FLOP # React to being shot from death poses + BOTH_DEAD1_FLOP, //# React to being shot from First Death finished pose + BOTH_DEAD2_FLOP, //# React to being shot from Second Death finished pose + BOTH_DEAD3_FLOP, //# React to being shot from Third Death finished pose + BOTH_DEAD4_FLOP, //# React to being shot from Fourth Death finished pose + BOTH_DEAD5_FLOP, //# React to being shot from Fifth Death finished pose + BOTH_DEADFORWARD1_FLOP, //# React to being shot First thrown forward death finished pose + BOTH_DEADFORWARD2_FLOP, //# React to being shot Second thrown forward death finished pose + BOTH_DEADBACKWARD1_FLOP, //# React to being shot First thrown backward death finished pose + BOTH_DEADBACKWARD2_FLOP, //# React to being shot Second thrown backward death finished pose + BOTH_LYINGDEAD1_FLOP, //# React to being shot Killed lying down death finished pose + BOTH_STUMBLEDEAD1_FLOP, //# React to being shot Stumble forward death finished pose + BOTH_FALLDEAD1_FLOP, //# React to being shot Fall forward and splat death finished pose + //# #sep BOTH_ PAINS + BOTH_PAIN1, //# First take pain anim + BOTH_PAIN2, //# Second take pain anim + BOTH_PAIN3, //# Third take pain anim + BOTH_PAIN4, //# Fourth take pain anim + BOTH_PAIN5, //# Fifth take pain anim - from behind + BOTH_PAIN6, //# Sixth take pain anim - from behind + BOTH_PAIN7, //# Seventh take pain anim - from behind + BOTH_PAIN8, //# Eigth take pain anim - from behind + //# #sep BOTH_ ATTACKS + BOTH_ATTACK1, //# Attack with generic 1-handed weapon + BOTH_ATTACK2, //# Attack with generic 2-handed weapon + BOTH_ATTACK3, //# Attack with heavy 2-handed weapon + BOTH_ATTACK4, //# Attack with ??? + BOTH_ATTACK5, //# Attack with rocket launcher + BOTH_MELEE1, //# First melee attack + BOTH_MELEE2, //# Second melee attack + BOTH_MELEE3, //# Third melee attack + BOTH_MELEE4, //# Fourth melee attack + BOTH_MELEE5, //# Fifth melee attack + BOTH_MELEE6, //# Sixth melee attack + //# #sep BOTH_ STANDING + BOTH_STAND1, //# Standing idle, no weapon, hands down + BOTH_STAND1_RANDOM1, //# Random standing idle + BOTH_STAND1_RANDOM2, //# Random standing idle + BOTH_STAND1_RANDOM3, //# Random standing idle + BOTH_STAND1_RANDOM4, //# Random standing idle + BOTH_STAND1_RANDOM5, //# Random standing idle + BOTH_STAND1_RANDOM6, //# Random standing idle + BOTH_STAND1_RANDOM7, //# Random standing idle + BOTH_STAND1_RANDOM8, //# Random standing idle + BOTH_STAND1_RANDOM9, //# Random standing idle + BOTH_STAND1_RANDOM10, //# Random standing idle + BOTH_STAND1_RANDOM11, //# Random standing idle + BOTH_STAND1_RANDOM12, //# Random standing idle + BOTH_STAND1_RANDOM13, //# Random standing idle + BOTH_STAND1_RANDOM14, //# Random standing idle + BOTH_STAND2, //# Standing idle with a weapon + BOTH_STAND2_RANDOM1, //# Random standing idle + BOTH_STAND2_RANDOM2, //# Random standing idle + BOTH_STAND2_RANDOM3, //# Random standing idle + BOTH_STAND2_RANDOM4, //# Random standing idle + BOTH_STAND3, //# Standing hands behind back, at ease, etc. + BOTH_STAND4, //# two handed, gun down, relaxed stand + BOTH_STAND5, //# two handed, gun up, relaxed stand + BOTH_STAND6, //# one handed, gun at side, relaxed stand + BOTH_STAND7, //# (Chell) timid stance while looking around slightly and breathing + BOTH_STAND8, //# breathing after exherting oneself one handed + BOTH_STAND9, //# breathing after exherting oneself two handed + BOTH_STAND1TO3, //# Transition from stand1 to stand3 + BOTH_STAND3TO1, //# Transition from stand3 to stand1 + + BOTH_STAND2TO4, //# Transition from stand2 to stand4 + BOTH_STAND4TO2, //# Transition from stand4 to stand2 + BOTH_STANDTOWALK1, //# Transition from stand1 to walk1 + BOTH_STANDTOCONSOLE1, //# a transition from stand animations to console animations + BOTH_STANDUP1, //# standing up and stumbling + BOTH_TALKGESTURE1, //# standing up and talking + BOTH_TALKGESTURE2, //# standing up and talking + BOTH_TALKGESTURE3, //# standing up and talking + + BOTH_HELP1, //# helping hold injured4 man. + + BOTH_LEAN1, //# leaning on a railing + BOTH_LEAN1TODROPHELM, //# transition from LEAN1 to DROPHELM + + BOTH_CONSOLE1, //# Using a waist-high console with both hands + BOTH_CONSOLE1IDLE, //# Idle of CONSOLE1 + BOTH_CONSOLE1RIGHT, //# Reach right from CONSOLE1 + BOTH_CONSOLE1LEFT, //# Reach left from CONSOLE1 + BOTH_CONSOLE2, //# Using a head-high wall console with the right hand + BOTH_CONSOLE3, //# arms parallel to ground and typing similar to con.1 + BOTH_CONSOLE3IDLE, //# arms parallel to ground and typing similar to con.1 + BOTH_CONSOLE3RIGHT, //# arms parallel to ground and typing similar to con.1 + BOTH_CONSOLE3LEFT, //# arms parallel to ground and typing similar to con.1 + BOTH_CONSOLETOSTAND1, //# a transition from console animations to stand animations + + BOTH_GUARD_LOOKAROUND1, //# Cradling weapon and looking around + BOTH_GUARD_IDLE1, //# Cradling weapon and standing + BOTH_GUARD_LKRT1, //# cin17, quick glance right to sound of door slamming + BOTH_ALERT1, //# Startled by something while on guard + BOTH_GESTURE1, //# Generic gesture, non-specific + BOTH_GESTURE2, //# Generic gesture, non-specific + BOTH_GESTURE3, //# Generic gesture, non-specific + BOTH_CROWDLOOK1, //# Person staring out into space 1 + BOTH_CROWDLOOK2, //# Person staring out into space 2 + BOTH_CROWDLOOK3, //# Person staring out into space 3 + BOTH_CROWDLOOK4, //# Person staring out into space 4 + BOTH_GRAB1, //# Grabbing something from table + BOTH_GRAB2, //# Grabbing something from table + BOTH_GRAB3, //# Grabbing something from table + BOTH_GRABBED1, //# cin9.3 chell being grabbed 180 from munro, 28 pixels away + BOTH_GRABBED2, //# cin9.3 idle grabbed 180 from munro, 28 pixels away + BOTH_SURPRISED1, //# Surprised reaction 1 + BOTH_SURPRISED2, //# Surprised reaction 2 + BOTH_SURPRISED3, //# Surprised reaction 3 + BOTH_SURPRISED4, //# Surprised reaction 4 + BOTH_SURPRISED5, //# Surprised reaction 5 + BOTH_SCARED1, //# Scared reaction 1 + BOTH_SCARED2, //# Scared reaction 2 + BOTH_CATCH1, //# Reaching to catch something falling + BOTH_POSSESSED1, //# 7 of 9 possessed + BOTH_POSSESSED2, //# 7 of 9 possessed with hand out + BOTH_SNAPTO1, //# cin.23, 7o9 coming to from borg possession + BOTH_SNAPTO2, //# cin.23, 7o9 coming to from borg possession2 + BOTH_DROPANGERWEAP2, //# cin.23, Nelson lowering weapon in anger + BOTH_SHOCK1, //# telsia being zapped by electricity cinematic 9.2 + BOTH_PSYCHICSHOCK1, //# having visions of the boss + BOTH_PSYCHICSHOCK2, //# having visions of the boss + BOTH_ASSIMILATED1, //# Cin.18, Foster being assimilated by borg + BOTH_FALSEJUMP1, //# Biessman pretending to jump down on Chell + BOTH_LAUGH1, //# squat pose of Biessman laughing at Chell + BOTH_LAUGH2, //# standing laugh of mocking Biessman + BOTH_ACTIVATEBELT1, //# activating transport buffer on belt + + BOTH_GROUNDSHAKE1, //# Bracing self when ground shakes beneath him + BOTH_GROUNDSHAKE2, //# Falling to knees and shileding self, then standing + + BOTH_READYWEAPON1, //# cin17, comes from greeting, just before fighting + + BOTH_SPAWN1, //# Spawning in to the world + BOTH_TALK1, //# Generic talk anim + + BOTH_COVERUP1_LOOP, //# animation of getting in line of friendly fire + BOTH_COVERUP1_START, //# transitions from stand to coverup1_loop + BOTH_COVERUP1_END, //# transitions from coverup1_loop to stand + BOTH_HEROSTANCE1, //# Biessman in the final shootout + BOTH_GUILT1, //# Player has a guilty conscience after shooting a teammate. + + BOTH_INJURED4, //# Injured pose 4 + BOTH_INJURED4TO5, //# Transition from INJURED4 to INJURED5 + BOTH_INJURED5, //# Injured pose 5 + + //# #sep BOTH_ SITTING/CROUCHING + BOTH_SIT1STAND, //# Stand up from First sitting anim + BOTH_SIT1TO2, //# Trans from sit1 to sit2? + BOTH_SIT1TO3, //# Trans from sit1 to sit3? + BOTH_SIT2TO1, //# Trans from sit2 to sit1? + BOTH_SIT2TO3, //# Trans from sit2 to sit3? + BOTH_SIT3TO1, //# Trans from sit3 to sit1? + BOTH_SIT3TO2, //# Trans from sit3 to sit2? + + BOTH_SIT4TO5, //# Trans from sit4 to sit5 + BOTH_SIT4TO6, //# Trans from sit4 to sit6 + BOTH_SIT5TO4, //# Trans from sit5 to sit4 + BOTH_SIT5TO6, //# Trans from sit5 to sit6 + BOTH_SIT6TO4, //# Trans from sit6 to sit4 + BOTH_SIT6TO5, //# Trans from sit6 to sit5 + BOTH_SIT7, //# sitting with arms over knees, no weapon + BOTH_SIT7TOSTAND1, //# getting up from sit7 into stand1 + + BOTH_TABLE_EAT1, //# Sitting at a table eating + BOTH_TABLE_CHEW1, //# Sitting at a table chewing + BOTH_TABLE_WIPE1, //# Sitting at a table wiping mouth + BOTH_TABLE_DRINK1, //# Sitting at a table drinking + BOTH_TABLE_GETUP1, //# Getting up from table + BOTH_TABLE_DEATH1, //# Dying while sitting at a table + BOTH_TABLE_IDLE1, //# Sitting at table breathing + BOTH_TABLE_TALKGESTURE1,//# Sitting at table gesturing while talking + BOTH_TABLE_GESTURE1, //# Sitting at table gesturing + BOTH_TABLE_GESTURE2, //# Sitting at table gesturing + + BOTH_CROUCH1, //# Transition from standing to crouch + BOTH_CROUCH1IDLE, //# Crouching idle + BOTH_CROUCH1WALK, //# Walking while crouched + BOTH_UNCROUCH1, //# Transition from crouch to standing + BOTH_CROUCH2IDLE, //# crouch and resting on back righ heel, no weapon + BOTH_CROUCH2TOSTAND1, //# going from crouch2 to stand1 + BOTH_GET_UP1, //# Get up from the ground, face down + BOTH_GET_UP2, //# Get up from the ground, face up + + BOTH_BENCHSIT1_IDLE, //# sitting on haz-locker room benches + BOTH_BENCHSIT1TO2, //# Trans from benchsit1 to benchsit2 + BOTH_BENCHSIT2TO1, //# Trans from benchsit2 to benchsit1 + BOTH_BENCHSIT2STAND, //# Trans from benchsit to standing + BOTH_BENCHSIT2_IDLE, //# sitting on haz-locker room benches + BOTH_BENCHSIT1_2STAND, //# getting up to stand from sitting on haz-benches + BOTH_BENCHSIT1_FIXBOOT, //# sitting on bench - pulling on/adjusting boot top + BOTH_BENCHSTAND1TO2, //# transition from stand to benchstand2 + BOTH_BENCHSTAND2, //# standing with right foot up on bench + BOTH_BENCHSTAND2TO1, //# transition from benchstand2 to stand + + BOTH_COUCHSIT1_IDLE, //# sitting in couch - haz lounge area + BOTH_COUCHSIT1_TO2, //# sitting in couch - lean back to 2nd position + BOTH_COUCHSIT1_2STAND1, //# getting up from couchsit1 to stand1 + BOTH_COUCHSIT1_TALKGESTURE, //# sitting in couch - talking with hands + BOTH_COUCHSIT1_GESTURELEFT, //# sitting in couch - talk gesture to the left + BOTH_COUCHSIT1_GESTURERIGHT,//# sitting in couch - talk gesture to the right + + BOTH_KNEELHAND1, //# Jurot puts hand to Munro's face, then pulls away + + //# #sep BOTH_ MOVING + BOTH_WALK1, //# Normal walk + BOTH_WALK2, //# Normal walk + BOTH_WALK3, //# Goes with stand3 + BOTH_WALK4, //# Walk cycle goes to a stand4 + BOTH_WALKTORUN1, //# transition from walk to run + BOTH_RUN1, //# Full run + BOTH_RUN1START, //# Start into full run1 + BOTH_RUN1STOP, //# Stop from full run1 + BOTH_RUN2, //# Full run + BOTH_RUNINJURED1, //# Run with injured left leg + BOTH_STRAFE_LEFT1, //# Sidestep left, should loop + BOTH_STRAFE_RIGHT1, //# Sidestep right, should loop + BOTH_TURN_LEFT1, //# Turn left, should loop + BOTH_TURN_RIGHT1, //# Turn right, should loop + BOTH_RUNAWAY1, //# Running scared + BOTH_SWIM1, //# Swimming + BOTH_JUMP1, //# Jump - wind-up and leave ground + BOTH_INAIR1, //# In air loop (from jump) + BOTH_LAND1, //# Landing (from in air loop) + BOTH_LAND2, //# Landing Hard (from a great height) + BOTH_JUMPBACK1, //# Jump backwards - wind-up and leave ground + BOTH_INAIRBACK1, //# In air loop (from jump back) + BOTH_LANDBACK1, //# Landing backwards(from in air loop) + BOTH_DIVE1, //# Dive! + BOTH_ROLL1_LEFT, //# Roll to left side + BOTH_ROLL1_RIGHT, //# Roll to right side + BOTH_LADDER_UP1, //# Climbing up a ladder with rungs at 16 unit intervals + BOTH_LADDER_DWN1, //# Climbing down a ladder with rungs at 16 unit intervals + BOTH_LADDER_IDLE, //# Just sitting on the ladder + BOTH_ONLADDER_BOT1, //# Getting on the ladder at the bottom + BOTH_OFFLADDER_BOT1, //# Getting off the ladder at the bottom + BOTH_ONLADDER_TOP1, //# Getting on the ladder at the top + BOTH_OFFLADDER_TOP1, //# Getting off the ladder at the top + BOTH_LIFT1, //# Lifting someone/thing over their shoulder + BOTH_STEP1, //# telsia checking out lake cinematic9.2 + BOTH_HITWALL1, //# cin.18, Kenn hit by borg into wall 56 units away + BOTH_AMBUSHLAND1, //# landing from fall on victim + BOTH_BIRTH1, //# birth from jumping through walls + + BOTH_SHIELD1, //# cin.6, munro's initial reaction to explosion + BOTH_SHIELD2, //# cin.6, munro in shielding position looping + BOTH_WALKPUSH1, //# man pushing crate + BOTH_PUSHTOSTAND1, //# man coming from pushing crate to stand1 + BOTH_HALT1, //# munro being grabbed by telsia before going in core room + + //# #sep BOTH_ FLYING IDLE + BOTH_FLY_IDLE1, //# Flying Idle 1 + BOTH_FLY_IDLE2, //# Flying Idle 2 + + + //# #sep BOTH_ FLYING MOVING + BOTH_FLY_START1, //# Start flying + BOTH_FLY_STOP1, //# Stop flying + BOTH_FLY_LOOP1, //# Normal flying, should loop + BOTH_FLOAT1, //# Crew floating through space 1 + BOTH_FLOAT2, //# Crew floating through space 2 + BOTH_FLOATCONSOLE1, //# Crew floating and working on console + + //# #sep BOTH_ LYING + BOTH_LIE_DOWN1, //# From a stand position, get down on ground, face down + BOTH_LIE_DOWN2, //# From a stand position, get down on ground, face up + BOTH_LIE_DOWN3, //# reaction to local disnode being destroyed + BOTH_PAIN2WRITHE1, //# Transition from upright position to writhing on ground anim + BOTH_PRONE2RLEG, //# Lying on ground reach to grab right leg + BOTH_PRONE2LLEG, //# Lying on ground reach to grab left leg + BOTH_WRITHING1, //# Lying on ground on back writhing in pain + BOTH_WRITHING1RLEG, //# Lying on ground writhing in pain, holding right leg + BOTH_WRITHING1LLEG, //# Lying on ground writhing in pain, holding left leg + BOTH_WRITHING2, //# Lying on ground on front writhing in pain + BOTH_INJURED1, //# Lying down, against wall - can also be sleeping against wall + BOTH_INJURED2, //# Injured pose 2 + BOTH_INJURED3, //# Injured pose 3 + BOTH_INJURED6, //# Injured pose 6 + BOTH_INJURED6ATTACKSTART, //# Start attack while in injured 6 pose + BOTH_INJURED6ATTACKSTOP, //# End attack while in injured 6 pose + BOTH_INJURED6COMBADGE, //# Hit combadge while in injured 6 pose + BOTH_INJURED6POINT, //# Chang points to door while in injured state + BOTH_INJUREDTOSTAND1, //# Runinjured to stand1 + + BOTH_CRAWLBACK1, //# Lying on back, crawling backwards with elbows + BOTH_SITWALL1, //# Sitting against a wall + BOTH_SLEEP1, //# laying on back-rknee up-rhand on torso + BOTH_SLEEP2, //# on floor-back against wall-arms crossed + BOTH_SLEEP3, //# Sleeping in a chair + BOTH_SLEEP4, //# Sleeping slumped over table + BOTH_SLEEP5, //# Laying on side sleeping on flat sufrace + BOTH_SLEEP1GETUP, //# alarmed and getting up out of sleep1 pose to stand + BOTH_SLEEP1GETUP2, //# + BOTH_SLEEP2GETUP, //# alarmed and getting up out of sleep2 pose to stand + BOTH_SLEEP3GETUP, //# alarmed and getting up out of sleep3 pose to stand + BOTH_SLEEP3DEATH, //# death in chair, from sleep3 idle + BOTH_SLEEP3DEAD, //# death in chair, from sleep3 idle + + BOTH_SLEEP_IDLE1, //# rub face and nose while asleep from sleep pose 1 + BOTH_SLEEP_IDLE2, //# shift position while asleep - stays in sleep2 + BOTH_SLEEP_IDLE3, //# Idle anim from sleep pose 3 + BOTH_SLEEP_IDLE4, //# Idle anim from sleep pose 4 + BOTH_SLEEP1_NOSE, //# Scratch nose from SLEEP1 pose + BOTH_SLEEP2_SHIFT, //# Shift in sleep from SLEEP2 pose + BOTH_RESTRAINED1, //# Telsia tied to medical table + BOTH_RESTRAINED1POINT, //# Telsia tied to medical table pointing at Munro + BOTH_LIFTED1, //# Fits with BOTH_LIFT1, lifted on shoulder + BOTH_CARRIED1, //# Fits with TORSO_CARRY1, carried over shoulder + BOTH_CARRIED2, //# Laying over object + + //# #sep BOTH_ BORG-SPECIFIC + BOTH_PLUGIN1, //# Borg plugs self in to alcove + BOTH_PLUGGEDIN1, //# Last frame of Borg plug in sequence + BOTH_PLUGOUT1, //# Borg unplugs self from alcove + + //# #sep BOTH_ HUNTER-SEEKER BOT-SPECIFIC + BOTH_POWERUP1, //# Wakes up + + + //================================================= + //ANIMS IN WHICH ONLY THE UPPER OBJECTS ARE IN MD3 + //================================================= + //# #sep TORSO_ WEAPON-RELATED + TORSO_DROPWEAP1, //# Put weapon away + TORSO_DROPWEAP2, //# Put weapon away + TORSO_DROPWEAP3, //# Put weapon away + TORSO_RAISEWEAP1, //# Draw Weapon + TORSO_RAISEWEAP2, //# Draw Weapon + TORSO_RAISEWEAP3, //# Draw Weapon + TORSO_WEAPONREADY1, //# Ready to fire 1 handed weapon + TORSO_WEAPONREADY2, //# Ready to fire 2 handed weapon + TORSO_WEAPONREADY3, //# Ready to fire heavy 2 handed weapon + TORSO_WEAPONIDLE1, //# Holding 1 handed weapon + TORSO_WEAPONIDLE2, //# Holding 2 handed weapon + TORSO_WEAPONIDLE3, //# Holding heavy 2 handed weapon + + //# #sep TORSO_ USING NON-WEAPON OBJECTS + TORSO_TRICORDER1, //# Using a tricorder + TORSO_MEDICORDER1, //# Using a Medical Tricorder + TORSO_PADD1, //# Using a PADD + TORSO_EQUIPMENT1, //# Twisting pipe with both hands + TORSO_EQUIPMENT2, //# Fidgiting with cylinder with both hands + TORSO_EQUIPMENT3, //# Using equipment one handed + TORSO_WRIST1, //# cin.24, Chang detonating bomb with wrist device + + //# #sep TORSO_ MISC + TORSO_COMBADGE1, //# Touch right hand to left breast + TORSO_COMBADGE2, //# Touch left hand to left breast + TORSO_COMBADGE3, //# Combadge touch from stand4 + TORSO_REDALERT1, //# Hitting comm button on wall with hand (Kirk-like) + TORSO_HANDGESTURE1, //# gestures to left one hand + TORSO_HANDGESTURE2, //# gestures to right one hand + TORSO_HANDGESTURE3, //# gestures to the left both hands + TORSO_HANDGESTURE4, //# gestures to the right both hands + TORSO_HANDGESTURE5, //# ? + TORSO_HANDGESTURE6, //# pointing (flank right) while talking & holding a weapon + TORSO_HANDGESTURE7, //# pointing (forward) while talking & holding a weapon + TORSO_HANDGESTURE8, //# pointing (flank left) while talking & holding a weapon + TORSO_HANDGESTURE9, //# quick point right from stand 4 + TORSO_HANDGESTURE10, //# quick point forward from stand 4 + TORSO_HANDGESTURE11, //# quick point left from stand 4 + TORSO_HANDGESTURE12, //# gesturing with both hands forward + TORSO_HANDGESTURE13, //# gesturing a shrug as if not knowing answer + + TORSO_HEADNOD1, //# nod in affirmation + TORSO_HEADSHAKE1, //# head goes down while shaking left and right in dissapointment + TORSO_HYPOSPRAY1, //# man giving hypo to people + TORSO_HYPOSPRAY4, //# using hypospray on telsia in scav5 + + TORSO_HANDEXTEND1, //# doctor reaching for hypospray in scav5 + TORSO_HANDRETRACT1, //# doctor taking hypospray from player in scav5 + + TORSO_DROPHELMET1, //# Drop the helmet to the waist + TORSO_RAISEHELMET1, //# Bring the helmet to the head + TORSO_REACHHELMET1, //# reaching for helmet off of 60 tall cabinet + TORSO_GRABLBACKL, //# reach to lower back with left hand + TORSO_GRABUBACKL, //# reach to upper back with left hand + TORSO_GRABLBACKR, //# reach to lower back with right hand + TORSO_GRABUBACKR, //# reach to upper back with right hand + + TORSO_STAND2TOWEAPONREADY2, //# cin.23, Nelson raising weapon in alarm and ready to fire + + TORSO_HAND1, //# Exchanging items - giver + TORSO_HAND2, //# Exchanging items - receiver + + TORSO_POKERIDLE1, //# holding cards + TORSO_POKERIDLE2, //# re-arranging cards + TORSO_POKERIDLE3, //# put card on table + + TORSO_SPEECHLESS1, //# hanging head in grief 1 + TORSO_SPEECHLESS2, //# hanging head in grief 2 + + TORSO_SHOUT1, //# left hand to mouth + TORSO_CARRY1, //# Carrying someone/thing over their shoulder (can go from BOTH_LIFT1) + + + + //================================================= + //ANIMS IN WHICH ONLY THE LOWER OBJECTS ARE IN MD3 + //================================================= + //# #sep Legs-only anims + LEGS_WALKBACK1, //# Walk1 backwards + LEGS_WALKBACK2, //# Walk2 backwards + LEGS_RUNBACK1, //# Run1 backwards + LEGS_RUNBACK2, //# Run2 backwards + LEGS_TURN1, //# What legs do when you turn your lower body to match your upper body facing + LEGS_TURN2, //# Leg turning from stand2 + LEGS_LEAN_LEFT1, //# Lean left + LEGS_LEAN_RIGHT1, //# Lean Right + LEGS_KNEELDOWN1, //# Get down on one knee? + LEGS_KNEELUP1, //# Get up from one knee? + LEGS_CRLEAN_LEFT1, //# Crouch Lean left + LEGS_CRLEAN_RIGHT1, //# Crouch Lean Right + + //# #eol + MAX_ANIMATIONS, +} animNumber_t; + +*/ + + + +// animations +/*typedef enum { + BOTH_ACTIVATEBELT1, + BOTH_ACTIVATEMEDKIT1, + BOTH_ASSIMILATED1, + BOTH_ATTACK1, + BOTH_ATTACK3, + BOTH_BENCHSIT1TO2, + BOTH_BENCHSIT1_2STAND, + BOTH_BENCHSIT1_FIXBOOT, + BOTH_BENCHSIT1_IDLE, + BOTH_BENCHSIT2STAND, + BOTH_BENCHSIT2TO1, + BOTH_BENCHSTAND2, + BOTH_BENCHSTAND2TO1, + BOTH_CATCH1, + BOTH_CONSOLE1, + BOTH_CONSOLE1IDLE, + BOTH_CONSOLE1LEFT, + BOTH_CONSOLE1RIGHT, + BOTH_CONSOLE2, + BOTH_CONSOLE3, + BOTH_CONSOLE3IDLE, + BOTH_CONSOLE3LEFT, + BOTH_CONSOLE3RIGHT, + BOTH_CONSOLETOSTAND1, + BOTH_COUCHSIT1_2STAND1, + BOTH_COUCHSIT1_GESTURELEFT, + BOTH_COUCHSIT1_GESTURERIGHT, + BOTH_COUCHSIT1_IDLE, + BOTH_COUCHSIT1_TALKGESTURE, + BOTH_CRAWLBACK1, + BOTH_CROUCH1, + BOTH_CROUCH1IDLE, + BOTH_CROUCH1WALK, + BOTH_CROUCH2IDLE, + BOTH_CROUCH2TOSTAND1, + BOTH_CROWDLOOK1, + BOTH_CROWDLOOK2, + BOTH_CROWDLOOK3, + BOTH_CROWDLOOK4, + BOTH_DEATH1, + BOTH_DEAD1, + BOTH_DEATH2, + BOTH_DEAD2, + BOTH_DEATHBACKWARD1, + BOTH_DEADBACKWARD1, + BOTH_DEATHBACKWARD2, + BOTH_DEADBACKWARD2, + BOTH_DEATHFORWARD1, + BOTH_DEADFORWARD1, + BOTH_DEATHFORWARD2, + BOTH_DEADFORWARD2, + BOTH_DIVE1, + BOTH_DROPANGERWEAP2, + BOTH_FALLDEATH1, + BOTH_FALLDEATH1INAIR, + BOTH_FALLDEATH1LAND, + BOTH_FALLDEAD1LAND, + BOTH_FALSEJUMP1, + BOTH_FLOAT1, + BOTH_FLOAT2, + BOTH_FLOATCONSOLE1, + BOTH_GET_UP1, + BOTH_GRAB1, + BOTH_GRAB2, + BOTH_GRABBED1, + BOTH_GRABBED2, + BOTH_GROUNDSHAKE1, + BOTH_GROUNDSHAKE2, + BOTH_GUARD_IDLE1, + BOTH_GUARD_LKRT1, + BOTH_GUARD_LOOKAROUND1, + BOTH_HALT1, + BOTH_HITWALL1, + BOTH_INAIR1, + BOTH_INJURED1, + BOTH_INJURED2, + BOTH_INJURED3, + BOTH_INJURED6, + BOTH_INJURED6ATTACKSTART, + BOTH_INJURED6ATTACKSTOP, + BOTH_INJURED6COMBADGE, + BOTH_JUMP1, + BOTH_LADDER_DWN1, + BOTH_LADDER_UP1, + BOTH_LADDER_IDLE, + BOTH_LAND1, + BOTH_LAUGH1, + BOTH_LAUGH2, + BOTH_LEAN1, + BOTH_LEAN1TODROPHELM, + BOTH_LYINGDEATH1, + BOTH_LYINGDEAD1, + BOTH_OFFLADDER_BOT1, + BOTH_OFFLADDER_TOP1, + BOTH_ONLADDER_BOT1, + BOTH_ONLADDER_TOP1, + BOTH_PAIN1, + BOTH_PAIN2, + BOTH_PAIN2WRITHE1, + BOTH_PSYCHICSHOCK1, + BOTH_PUSHTOSTAND1, + BOTH_RUN1, + BOTH_RUN1STOP, + BOTH_RUN2, + BOTH_RUNINJURED1, + BOTH_SCARED1, + BOTH_SCARED2, + BOTH_SHIELD1, + BOTH_SHIELD2, + BOTH_SIT1STAND, + BOTH_SIT1TO2, + BOTH_SIT1TO3, + BOTH_SIT2TO1, + BOTH_SIT2TO3, + BOTH_SIT3TO1, + BOTH_SIT3TO2, + BOTH_SIT4TO5, + BOTH_SIT4TO6, + BOTH_SIT5TO4, + BOTH_SIT5TO6, + BOTH_SIT6TO4, + BOTH_SIT6TO5, + BOTH_SIT7, + BOTH_SIT7TOSTAND1, + BOTH_STAND1, + BOTH_STAND1_RANDOM1, + BOTH_STAND1_RANDOM11, + BOTH_STAND1_RANDOM12, + BOTH_STAND1_RANDOM13, + BOTH_STAND1_RANDOM2, + BOTH_STAND1_RANDOM3, + BOTH_STAND1_RANDOM4, + BOTH_STAND1_RANDOM5, + BOTH_STAND1_RANDOM6, + BOTH_STAND1_RANDOM7, + BOTH_STAND1_RANDOM8, + BOTH_STAND2TO4, + BOTH_STAND2_RANDOM1, + BOTH_STAND2_RANDOM2, + BOTH_STAND2_RANDOM3, + BOTH_STAND3, + BOTH_STAND4, + BOTH_STAND4TO2, + BOTH_STAND5, + BOTH_STAND6, + BOTH_STAND7, + BOTH_STAND8, + BOTH_STAND9, + BOTH_STANDTOCONSOLE1, + BOTH_STANDTOWALK1, + BOTH_SURPRISED1, + BOTH_SURPRISED2, + BOTH_SURPRISED3, + BOTH_SURPRISED4, + BOTH_TABLE_EAT1, + BOTH_TABLE_GETUP1, + BOTH_TABLE_IDLE1, + BOTH_TABLE_TALKGESTURE1, + BOTH_UNCROUCH1, + BOTH_WALK1, + BOTH_WALK2, + BOTH_WALK4, + BOTH_WALKPUSH1, + BOTH_WALKTORUN1, + BOTH_WRITHING1, + BOTH_INJURED6POINT, + BOTH_COVERUP1_START, + BOTH_COVERUP1_LOOP, + BOTH_COVERUP1_END, + BOTH_GESTURE3, + BOTH_GESTURE2, + BOTH_GESTURE1, + BOTH_SURPRISED5, + BOTH_HEROSTANCE1, + BOTH_BENCHSIT2_IDLE, + BOTH_LANDBACK1, + BOTH_JUMPBACK1, + BOTH_GUILT1, + BOTH_WRITHING2, + BOTH_WALK3, + BOTH_WALK7, + BOTH_STAND2, + TORSO_ATTACK2, + TORSO_WEAPONREADY2, + TORSO_COMBADGE1, + TORSO_COMBADGE2, + TORSO_COMBADGE3, + TORSO_DROPHELMET1, + TORSO_DROPWEAP1, + TORSO_EQUIPMENT1, + TORSO_EQUIPMENT2, + TORSO_GRABLBACKL, + TORSO_HAND1, + TORSO_HAND2, + TORSO_HANDGESTURE1, + TORSO_HANDGESTURE10, + TORSO_HANDGESTURE11, + TORSO_HANDGESTURE12, + TORSO_HANDGESTURE13, + TORSO_HANDGESTURE2, + TORSO_HANDGESTURE3, + TORSO_HANDGESTURE4, + TORSO_HANDGESTURE6, + TORSO_HANDGESTURE7, + TORSO_HANDGESTURE8, + TORSO_HANDGESTURE9, + TORSO_HEADNOD1, + TORSO_HEADSHAKE1, + TORSO_HYPOSPRAY1, + TORSO_MEDICORDER1, + TORSO_POKERIDLE1, + TORSO_POKERIDLE2, + TORSO_POKERIDLE3, + TORSO_RAISEHELMET1, + TORSO_RAISEWEAP1, + TORSO_REACHHELMET1, + TORSO_SHOUT1, + TORSO_SPEECHLESS1, + TORSO_SPEECHLESS2, + TORSO_STAND2TOWEAPONREADY2, + TORSO_TRICORDER1, + TORSO_WEAPONIDLE1, + TORSO_WRIST1, + TORSO_PADD1, + TORSO_WEAPONREADY1, + TORSO_COFFEE, + LEGS_KNEELDOWN1, + LEGS_KNEEL1, + LEGS_KNEELUP1, + LEGS_LEAN_LEFT1, + LEGS_LEAN_RIGHT1, + LEGS_TURN1, + LEGS_TURN2, + LEGS_WALKBACK1, + LEGS_BACK, + + MAX_ANIMATIONS +} animNumber_t;*/ + +typedef enum { + BOTH_ASSIMILATED1=0, + BOTH_ATTACK1, + BOTH_ATTACK2, + BOTH_ATTACK3, + BOTH_BENCHSIT1_2STAND, + BOTH_BENCHSIT1_FIXBOOT, + BOTH_BENCHSIT1_IDLE, + BOTH_BENCHSIT1TO2, + BOTH_BENCHSIT2_IDLE, + BOTH_BENCHSIT2TO1, + BOTH_BENCHSTAND1TO2, + BOTH_BENCHSTAND2, + BOTH_BENCHSTAND2TO1, + BOTH_CATCH1, + BOTH_CONSOLE1, + BOTH_CONSOLE1IDLE, + BOTH_CONSOLE1LEFT, + BOTH_CONSOLE1RIGHT, + BOTH_CONSOLE2, + BOTH_CONSOLE3, + BOTH_CONSOLE3IDLE, + BOTH_CONSOLE3LEFT, + BOTH_CONSOLE3RIGHT, + BOTH_CONSOLE4, + BOTH_CONSOLE5, + BOTH_COUCHSIT1_2STAND1, + BOTH_COUCHSIT1_GESTURELEFT, + BOTH_COUCHSIT1_GESTURERIGHT, + BOTH_COUCHSIT1_IDLE, + BOTH_COUCHSIT1_TALKGESTURE, + BOTH_COUCHSIT1_TO2, + BOTH_COUCHSIT2, + BOTH_COVERUP1_END, + BOTH_COVERUP1_LOOP, + BOTH_COVERUP1_START, + BOTH_COWAR1, + //BOTH_CROUCH1, + BOTH_CROUCH1IDLE, + BOTH_CROUCH1WALK, + BOTH_CROUCH2IDLE, + //BOTH_CROUCH2TOSTAND1, + BOTH_CROWDLOOK1, + BOTH_CROWDLOOK2, + BOTH_CROWDLOOK3, + BOTH_CROWDLOOK4, + //BOTH_DEAD1_FLOP, + //BOTH_DEAD2_FLOP, + //BOTH_DEADBACKWARD1_FLOP, + //BOTH_DEADBACKWARD2_FLOP, + //BOTH_DEADFORWARD1_FLOP, + //BOTH_DEADFORWARD2_FLOP, + BOTH_DEATH1, + BOTH_DEAD1, + BOTH_DEATH2, + BOTH_DEAD2, + BOTH_DEATHBACKWARD1, + BOTH_DEADBACKWARD1, + BOTH_DEATHBACKWARD2, + BOTH_DEADBACKWARD2, + BOTH_DEATHFORWARD1, + BOTH_DEADFORWARD1, + BOTH_DEATHFORWARD2, + BOTH_DEADFORWARD2, + BOTH_DIVE1, + //BOTH_FALLDEAD1_FLOP, + //BOTH_FALLDEATH1, + BOTH_FALLDEATH1INAIR, + BOTH_FALLDEATH1LAND, + BOTH_FALLDEAD1LAND, + BOTH_FLOAT1, + BOTH_FLOAT2, + //BOTH_GESTURE1, + BOTH_GESTURE2, + BOTH_GESTURE3, + BOTH_GET_UP1, + BOTH_GRAB1, + BOTH_GRAB2, + BOTH_GRAB3, + BOTH_GRAB4, + BOTH_GRABBED1, + BOTH_GRABBED2, + BOTH_GROUNDSHAKE1, + BOTH_GROUNDSHAKE1LOOP, + BOTH_GROUNDSHAKE2, + BOTH_GUARD_IDLE1, + BOTH_GUARD_LKRT1, + BOTH_GUARD_LOOKAROUND1, + BOTH_GUILT1, + BOTH_HITWALL1, + BOTH_HELP1, + BOTH_INJURED1, + BOTH_INJURED2, + BOTH_INJURED3, + BOTH_INJURED4, + BOTH_INJURED4TO5, + BOTH_INJURED5, + BOTH_INJURED6, + BOTH_INJURED6COMBADGE, + BOTH_INJURED6POINT, + //BOTH_INJUREDTOSTAND1, + BOTH_JUMP1, + BOTH_JUMPBACK1, + BOTH_KNEELHAND1, + BOTH_LADDER_DWN1, + BOTH_LADDER_UP1, + BOTH_LADDER_IDLE, + BOTH_LAND1, + BOTH_LANDBACK1, + BOTH_LAUGH1, + BOTH_LAUGH2, + BOTH_LEAN1, + //BOTH_LYINGDEAD1_FLOP, + BOTH_LYINGDEATH1, + BOTH_LYINGDEAD1, + BOTH_OFFLADDER_BOT1, + //BOTH_OFFLADDER_TOP1, + BOTH_ONLADDER_BOT1, + //BOTH_ONLADDER_TOP1, + BOTH_PAIN2WRITHE1, + BOTH_POSSESSED1, + BOTH_POSSESSED2, + BOTH_PSYCHICSHOCK1, + BOTH_PSYCHICSHOCK2, + BOTH_RUN1, + BOTH_RUN1STOP, + BOTH_RUN2, + BOTH_RUNAWAY1, + BOTH_RUNINJURED1, + BOTH_SCARED2, + BOTH_SHIELD1, + BOTH_SHIELD2, + BOTH_SIT1STAND, + BOTH_SIT1TO2, + BOTH_SIT1TO3, + BOTH_SIT1, + BOTH_SIT2TO1, + BOTH_SIT2TO3, + BOTH_SIT2, + BOTH_SIT3TO1, + BOTH_SIT3TO2, + BOTH_SIT3, + BOTH_SIT4TO5, + BOTH_SIT4TO6, + BOTH_SIT4, + BOTH_SIT5TO4, + BOTH_SIT5TO6, + BOTH_SIT5, + BOTH_SIT6TO4, + BOTH_SIT6TO5, + BOTH_SIT6, + BOTH_SIT7, + BOTH_SIT7TOSTAND1, + BOTH_SLEEP1, + BOTH_SLEEP1_NOSE, + BOTH_SLEEP1GETUP, + BOTH_SLEEP2, + BOTH_SLEEP2_SHIFT, + BOTH_SLEEP2GETUP, + BOTH_SLEEP3, + BOTH_SLEEP3DEATH, + BOTH_SLEEP3DEAD, + BOTH_SLEEP3GETUP, + BOTH_SNAPTO1, + BOTH_SNAPTO2, + BOTH_STAND1, + //BOTH_STAND1_RANDOM1, + BOTH_STAND1_RANDOM2, + BOTH_STAND1_RANDOM3, + BOTH_STAND1_RANDOM4, + BOTH_STAND1_RANDOM5, + BOTH_STAND1_RANDOM6, + BOTH_STAND1_RANDOM7, + BOTH_STAND1_RANDOM8, + BOTH_STAND1_RANDOM9, + BOTH_STAND1_RANDOM10, + BOTH_STAND1_RANDOM11, + BOTH_STAND2, + BOTH_STAND2_RANDOM1, + BOTH_STAND2_RANDOM2, + BOTH_STAND2_RANDOM3, + BOTH_STAND2_RANDOM4, + BOTH_STAND2_RANDOM5, + BOTH_STAND2_RANDOM6, + BOTH_STAND2_RANDOM7, + BOTH_STAND2_RANDOM8, + BOTH_STAND2_RANDOM9, + BOTH_STAND2_RANDOM10, + BOTH_STAND2_RANDOM11, + BOTH_STAND2_RANDOM12, + BOTH_STAND3, + BOTH_STAND4, + BOTH_STAND5, + BOTH_STAND6, + BOTH_STAND7, + BOTH_STAND8, + BOTH_STAND9, + BOTH_STANDUP1, + BOTH_SURPRISED1, + BOTH_SURPRISED2, + BOTH_SURPRISED3, + BOTH_SURPRISED4, + BOTH_SURPRISED5, + BOTH_TABLE_EAT1, + BOTH_TABLE_GETUP1, + BOTH_TABLE_IDLE1, + BOTH_TABLE_TALKGESTURE1, + BOTH_TALKGESTURE1, + BOTH_TALKGESTURE2, + //BOTH_TALKGESTURE3, + //BOTH_UNCROUCH1, + BOTH_WALK1, + BOTH_WALK2, + BOTH_WALK3, + BOTH_WALK4, + BOTH_WALK7, + BOTH_WRITHING1, + BOTH_SQUIRM1, + BOTH_SQUIRM1GETUP, + BOTH_SHAKE1, + BOTH_SHAKE2, + BOTH_WRITHING2, + TORSO_ACTIVATEMEDKIT1, + TORSO_COFFEE, + TORSO_COMBADGE1, + TORSO_COMBADGE2, + TORSO_COMBADGE3, + TORSO_COMBADGE4, + //TORSO_DROPHELMET1, + TORSO_DROPWEAP1, + TORSO_EQUIPMENT1, + TORSO_EQUIPMENT2, + TORSO_EQUIPMENT3, + TORSO_GRABLBACKL, + TORSO_HAND1, + TORSO_HAND2, + TORSO_HANDGESTURE1, + TORSO_HANDGESTURE2, + TORSO_HANDGESTURE3, + TORSO_HANDGESTURE4, + TORSO_HANDGESTURE5, + TORSO_HANDGESTURE6, + TORSO_HANDGESTURE7, + TORSO_HANDGESTURE8, + TORSO_HANDGESTURE9, + TORSO_HANDGESTURE10, + TORSO_HANDGESTURE11, + TORSO_HANDGESTURE12, + TORSO_HANDGESTURE13, + //TORSO_HEADNOD1, + //TORSO_HEADSHAKE1, + TORSO_HYPO1, + TORSO_HYPOSPRAY1, + TORSO_MEDICORDER1, + TORSO_PADD1, + TORSO_POKERIDLE1, + TORSO_POKERIDLE2, + TORSO_POKERIDLE3, + //TORSO_RAISEHELMET1, + TORSO_RAISEWEAP1, + //TORSO_REACHHELMET1, + TORSO_SHOUT1, + TORSO_SPEECHLESS1, + TORSO_SPEECHLESS2, + TORSO_TALKGESTURE4, + TORSO_TALKGESTURE5, + //TORSO_TALKGESTURE6, + TORSO_TRICORDER1, + TORSO_WEAPONIDLE1, + //TORSO_WEAPONIDLE2, + //TORSO_WEAPONIDLE3, + TORSO_WEAPONREADY1, + TORSO_WEAPONREADY2, + TORSO_WEAPONREADY3, + TORSO_WEAPONPOSE1, + TORSO_WRIST1, + TORSO_GESTURE, + LEGS_KNEEL1, + LEGS_RUNBACK2, + LEGS_TURN1, + LEGS_TURN2, + LEGS_WALKBACK1, + LEGS_SWIM, + + MAX_ANIMATIONS +} animNumber_t; + + +#ifndef cg_players_c +extern stringID_table_t animTable [MAX_ANIMATIONS+1]; +#else +stringID_table_t animTable[MAX_ANIMATIONS+1]= +{ + ENUM2STRING(BOTH_ASSIMILATED1), + ENUM2STRING(BOTH_ATTACK1), + ENUM2STRING(BOTH_ATTACK2), + ENUM2STRING(BOTH_ATTACK3), + ENUM2STRING(BOTH_BENCHSIT1_2STAND), + ENUM2STRING(BOTH_BENCHSIT1_FIXBOOT), + ENUM2STRING(BOTH_BENCHSIT1_IDLE), + ENUM2STRING(BOTH_BENCHSIT1TO2), + ENUM2STRING(BOTH_BENCHSIT2_IDLE), + ENUM2STRING(BOTH_BENCHSIT2TO1), + ENUM2STRING(BOTH_BENCHSTAND1TO2), + ENUM2STRING(BOTH_BENCHSTAND2), + ENUM2STRING(BOTH_BENCHSTAND2TO1), + ENUM2STRING(BOTH_CATCH1), + ENUM2STRING(BOTH_CONSOLE1), + ENUM2STRING(BOTH_CONSOLE1IDLE), + ENUM2STRING(BOTH_CONSOLE1LEFT), + ENUM2STRING(BOTH_CONSOLE1RIGHT), + ENUM2STRING(BOTH_CONSOLE2), + ENUM2STRING(BOTH_CONSOLE3), + ENUM2STRING(BOTH_CONSOLE3IDLE), + ENUM2STRING(BOTH_CONSOLE3LEFT), + ENUM2STRING(BOTH_CONSOLE3RIGHT), + ENUM2STRING(BOTH_CONSOLE4), + ENUM2STRING(BOTH_CONSOLE5), + ENUM2STRING(BOTH_COUCHSIT1_2STAND1), + ENUM2STRING(BOTH_COUCHSIT1_GESTURELEFT), + ENUM2STRING(BOTH_COUCHSIT1_GESTURERIGHT), + ENUM2STRING(BOTH_COUCHSIT1_IDLE), + ENUM2STRING(BOTH_COUCHSIT1_TALKGESTURE), + ENUM2STRING(BOTH_COUCHSIT1_TO2), + ENUM2STRING(BOTH_COUCHSIT2), + ENUM2STRING(BOTH_COVERUP1_END), + ENUM2STRING(BOTH_COVERUP1_LOOP), + ENUM2STRING(BOTH_COVERUP1_START), + ENUM2STRING(BOTH_COWAR1), + ENUM2STRING(BOTH_CROUCH1IDLE), + ENUM2STRING(BOTH_CROUCH1WALK), + ENUM2STRING(BOTH_CROUCH2IDLE), + ENUM2STRING(BOTH_CROWDLOOK1), + ENUM2STRING(BOTH_CROWDLOOK2), + ENUM2STRING(BOTH_CROWDLOOK3), + ENUM2STRING(BOTH_CROWDLOOK4), + ENUM2STRING(BOTH_DEATH1), + ENUM2STRING(BOTH_DEAD1), + ENUM2STRING(BOTH_DEATH2), + ENUM2STRING(BOTH_DEAD2), + ENUM2STRING(BOTH_DEATHBACKWARD1), + ENUM2STRING(BOTH_DEADBACKWARD1), + ENUM2STRING(BOTH_DEATHBACKWARD2), + ENUM2STRING(BOTH_DEADBACKWARD2), + ENUM2STRING(BOTH_DEATHFORWARD1), + ENUM2STRING(BOTH_DEADFORWARD1), + ENUM2STRING(BOTH_DEATHFORWARD2), + ENUM2STRING(BOTH_DEADFORWARD2), + ENUM2STRING(BOTH_DIVE1), + ENUM2STRING(BOTH_FALLDEATH1INAIR), + ENUM2STRING(BOTH_FALLDEATH1LAND), + ENUM2STRING(BOTH_FALLDEAD1LAND), + ENUM2STRING(BOTH_FLOAT1), + ENUM2STRING(BOTH_FLOAT2), + ENUM2STRING(BOTH_GESTURE2), + ENUM2STRING(BOTH_GESTURE3), + ENUM2STRING(BOTH_GET_UP1), + ENUM2STRING(BOTH_GRAB1), + ENUM2STRING(BOTH_GRAB2), + ENUM2STRING(BOTH_GRAB3), + ENUM2STRING(BOTH_GRAB4), + ENUM2STRING(BOTH_GRABBED1), + ENUM2STRING(BOTH_GRABBED2), + ENUM2STRING(BOTH_GROUNDSHAKE1), + ENUM2STRING(BOTH_GROUNDSHAKE2), + ENUM2STRING(BOTH_GUARD_IDLE1), + ENUM2STRING(BOTH_GUARD_LKRT1), + ENUM2STRING(BOTH_GUARD_LOOKAROUND1), + ENUM2STRING(BOTH_GUILT1), + ENUM2STRING(BOTH_HITWALL1), + ENUM2STRING(BOTH_HELP1), + ENUM2STRING(BOTH_INJURED1), + ENUM2STRING(BOTH_INJURED2), + ENUM2STRING(BOTH_INJURED3), + ENUM2STRING(BOTH_INJURED4), + ENUM2STRING(BOTH_INJURED4TO5), + ENUM2STRING(BOTH_INJURED5), + ENUM2STRING(BOTH_INJURED6), + ENUM2STRING(BOTH_INJURED6COMBADGE), + ENUM2STRING(BOTH_INJURED6POINT), + ENUM2STRING(BOTH_JUMP1), + ENUM2STRING(BOTH_JUMPBACK1), + ENUM2STRING(BOTH_KNEELHAND1), + ENUM2STRING(BOTH_LADDER_DWN1), + ENUM2STRING(BOTH_LADDER_UP1), + ENUM2STRING(BOTH_LADDER_IDLE), + ENUM2STRING(BOTH_LAND1), + ENUM2STRING(BOTH_LANDBACK1), + ENUM2STRING(BOTH_LAUGH1), + ENUM2STRING(BOTH_LAUGH2), + ENUM2STRING(BOTH_LEAN1), + ENUM2STRING(BOTH_LYINGDEATH1), + ENUM2STRING(BOTH_LYINGDEAD1), + ENUM2STRING(BOTH_OFFLADDER_BOT1), + ENUM2STRING(BOTH_ONLADDER_BOT1), + ENUM2STRING(BOTH_PAIN2WRITHE1), + ENUM2STRING(BOTH_POSSESSED1), + ENUM2STRING(BOTH_POSSESSED2), + ENUM2STRING(BOTH_PSYCHICSHOCK1), + ENUM2STRING(BOTH_PSYCHICSHOCK2), + ENUM2STRING(BOTH_RUN1), + ENUM2STRING(BOTH_RUN1STOP), + ENUM2STRING(BOTH_RUN2), + ENUM2STRING(BOTH_RUNAWAY1), + ENUM2STRING(BOTH_RUNINJURED1), + ENUM2STRING(BOTH_SCARED2), + ENUM2STRING(BOTH_SHIELD1), + ENUM2STRING(BOTH_SHIELD2), + ENUM2STRING(BOTH_SIT1STAND), + ENUM2STRING(BOTH_SIT1TO2), + ENUM2STRING(BOTH_SIT1TO3), + ENUM2STRING(BOTH_SIT1), + ENUM2STRING(BOTH_SIT2TO1), + ENUM2STRING(BOTH_SIT2TO3), + ENUM2STRING(BOTH_SIT2), + ENUM2STRING(BOTH_SIT3TO1), + ENUM2STRING(BOTH_SIT3TO2), + ENUM2STRING(BOTH_SIT3), + ENUM2STRING(BOTH_SIT4TO5), + ENUM2STRING(BOTH_SIT4TO6), + ENUM2STRING(BOTH_SIT4), + ENUM2STRING(BOTH_SIT5TO4), + ENUM2STRING(BOTH_SIT5TO6), + ENUM2STRING(BOTH_SIT5), + ENUM2STRING(BOTH_SIT6TO4), + ENUM2STRING(BOTH_SIT6TO5), + ENUM2STRING(BOTH_SIT6), + ENUM2STRING(BOTH_SIT7), + ENUM2STRING(BOTH_SIT7TOSTAND1), + ENUM2STRING(BOTH_SLEEP1), + ENUM2STRING(BOTH_SLEEP1_NOSE), + ENUM2STRING(BOTH_SLEEP1GETUP), + ENUM2STRING(BOTH_SLEEP2), + ENUM2STRING(BOTH_SLEEP2_SHIFT), + ENUM2STRING(BOTH_SLEEP2GETUP), + ENUM2STRING(BOTH_SLEEP3), + ENUM2STRING(BOTH_SLEEP3DEATH), + ENUM2STRING(BOTH_SLEEP3DEAD), + ENUM2STRING(BOTH_SLEEP3GETUP), + ENUM2STRING(BOTH_SNAPTO1), + ENUM2STRING(BOTH_SNAPTO2), + ENUM2STRING(BOTH_STAND1), + ENUM2STRING(BOTH_STAND1_RANDOM2), + ENUM2STRING(BOTH_STAND1_RANDOM3), + ENUM2STRING(BOTH_STAND1_RANDOM4), + ENUM2STRING(BOTH_STAND1_RANDOM5), + ENUM2STRING(BOTH_STAND1_RANDOM6), + ENUM2STRING(BOTH_STAND1_RANDOM7), + ENUM2STRING(BOTH_STAND1_RANDOM8), + ENUM2STRING(BOTH_STAND1_RANDOM9), + ENUM2STRING(BOTH_STAND1_RANDOM10), + ENUM2STRING(BOTH_STAND1_RANDOM11), + ENUM2STRING(BOTH_STAND2), + ENUM2STRING(BOTH_STAND2_RANDOM1), + ENUM2STRING(BOTH_STAND2_RANDOM2), + ENUM2STRING(BOTH_STAND2_RANDOM3), + ENUM2STRING(BOTH_STAND2_RANDOM4), + ENUM2STRING(BOTH_STAND2_RANDOM5), + ENUM2STRING(BOTH_STAND2_RANDOM6), + ENUM2STRING(BOTH_STAND2_RANDOM7), + ENUM2STRING(BOTH_STAND2_RANDOM8), + ENUM2STRING(BOTH_STAND2_RANDOM9), + ENUM2STRING(BOTH_STAND2_RANDOM10), + ENUM2STRING(BOTH_STAND2_RANDOM11), + ENUM2STRING(BOTH_STAND2_RANDOM12), + ENUM2STRING(BOTH_STAND3), + ENUM2STRING(BOTH_STAND4), + ENUM2STRING(BOTH_STAND5), + ENUM2STRING(BOTH_STAND6), + ENUM2STRING(BOTH_STAND7), + ENUM2STRING(BOTH_STAND8), + ENUM2STRING(BOTH_STAND9), + ENUM2STRING(BOTH_STANDUP1), + ENUM2STRING(BOTH_SURPRISED1), + ENUM2STRING(BOTH_SURPRISED2), + ENUM2STRING(BOTH_SURPRISED3), + ENUM2STRING(BOTH_SURPRISED4), + ENUM2STRING(BOTH_SURPRISED5), + ENUM2STRING(BOTH_TABLE_EAT1), + ENUM2STRING(BOTH_TABLE_GETUP1), + ENUM2STRING(BOTH_TABLE_IDLE1), + ENUM2STRING(BOTH_TABLE_TALKGESTURE1), + ENUM2STRING(BOTH_TALKGESTURE1), + ENUM2STRING(BOTH_TALKGESTURE2), + ENUM2STRING(BOTH_WALK1), + ENUM2STRING(BOTH_WALK2), + ENUM2STRING(BOTH_WALK3), + ENUM2STRING(BOTH_WALK4), + ENUM2STRING(BOTH_WALK7), + ENUM2STRING(BOTH_WRITHING1), + ENUM2STRING(BOTH_SQUIRM1), + ENUM2STRING(BOTH_SQUIRM1GETUP), + ENUM2STRING(BOTH_SHAKE1), + ENUM2STRING(BOTH_SHAKE2), + ENUM2STRING(BOTH_WRITHING2), + ENUM2STRING(TORSO_ACTIVATEMEDKIT1), + ENUM2STRING(TORSO_COFFEE), + ENUM2STRING(TORSO_COMBADGE1), + ENUM2STRING(TORSO_COMBADGE2), + ENUM2STRING(TORSO_COMBADGE3), + ENUM2STRING(TORSO_COMBADGE4), + ENUM2STRING(TORSO_DROPWEAP1), + ENUM2STRING(TORSO_EQUIPMENT1), + ENUM2STRING(TORSO_EQUIPMENT2), + ENUM2STRING(TORSO_EQUIPMENT3), + ENUM2STRING(TORSO_GRABLBACKL), + ENUM2STRING(TORSO_HAND1), + ENUM2STRING(TORSO_HAND2), + ENUM2STRING(TORSO_HANDGESTURE1), + ENUM2STRING(TORSO_HANDGESTURE2), + ENUM2STRING(TORSO_HANDGESTURE3), + ENUM2STRING(TORSO_HANDGESTURE4), + ENUM2STRING(TORSO_HANDGESTURE5), + ENUM2STRING(TORSO_HANDGESTURE6), + ENUM2STRING(TORSO_HANDGESTURE7), + ENUM2STRING(TORSO_HANDGESTURE8), + ENUM2STRING(TORSO_HANDGESTURE9), + ENUM2STRING(TORSO_HANDGESTURE10), + ENUM2STRING(TORSO_HANDGESTURE11), + ENUM2STRING(TORSO_HANDGESTURE12), + ENUM2STRING(TORSO_HANDGESTURE13), + ENUM2STRING(TORSO_HYPO1), + ENUM2STRING(TORSO_HYPOSPRAY1), + ENUM2STRING(TORSO_MEDICORDER1), + ENUM2STRING(TORSO_PADD1), + ENUM2STRING(TORSO_POKERIDLE1), + ENUM2STRING(TORSO_POKERIDLE2), + ENUM2STRING(TORSO_POKERIDLE3), + ENUM2STRING(TORSO_RAISEWEAP1), + ENUM2STRING(TORSO_SHOUT1), + ENUM2STRING(TORSO_SPEECHLESS1), + ENUM2STRING(TORSO_SPEECHLESS2), + ENUM2STRING(TORSO_TALKGESTURE4), + ENUM2STRING(TORSO_TALKGESTURE5), + ENUM2STRING(TORSO_TRICORDER1), + ENUM2STRING(TORSO_WEAPONIDLE1), + ENUM2STRING(TORSO_WEAPONREADY1), + ENUM2STRING(TORSO_WEAPONREADY2), + ENUM2STRING(TORSO_WEAPONREADY3), + ENUM2STRING(TORSO_WEAPONPOSE1), + ENUM2STRING(TORSO_WRIST1), + ENUM2STRING(TORSO_GESTURE), + ENUM2STRING(LEGS_KNEEL1), + ENUM2STRING(LEGS_RUNBACK2), + ENUM2STRING(LEGS_TURN1), + ENUM2STRING(LEGS_TURN2), + ENUM2STRING(LEGS_WALKBACK1), + ENUM2STRING(LEGS_SWIM), + NULL, -1 +}; + +/* +typedef enum { + BOTH_DEATH1, + BOTH_DEAD1, + BOTH_DEATH2, + BOTH_DEAD2, + BOTH_DEATH3, + BOTH_DEAD3, + + TORSO_GESTURE, + + TORSO_ATTACK, + TORSO_ATTACK2, + + TORSO_DROP, + TORSO_RAISE, + + TORSO_STAND, + TORSO_STAND2, + + LEGS_WALKCR, + LEGS_WALK, + LEGS_RUN, + LEGS_BACK, + LEGS_SWIM, + + LEGS_JUMP, + LEGS_LAND, + + LEGS_JUMPB, + LEGS_LANDB, + + LEGS_IDLE, + LEGS_IDLECR, + + LEGS_TURN, + + MAX_ANIMATIONS +} animNumber_t; +*/ + +#endif + + +//RPG-X: J2J - Added to help solve LNK2005 errors (special case for cg_players.c) +//#ifndef cg_players_c +/* +extern stringID_table_t animTable [MAX_ANIMATIONS+1]; + +#else +///////////////////////////////////////////////////////////////////////////////// + +//And this is used to load in the animation.cfg file. +stringID_table_t animTable [MAX_ANIMATIONS+1] = +{ + //================================================= + //ANIMS IN WHICH UPPER AND LOWER OBJECTS ARE IN MD3 + //================================================= + //# DEATHS + ENUM2STRING(BOTH_DEATH1), //# First Death anim + ENUM2STRING(BOTH_DEATH2), //# Second Death anim + ENUM2STRING(BOTH_DEATH3), //# Third Death anim + ENUM2STRING(BOTH_DEATH4), //# Fourth Death anim + ENUM2STRING(BOTH_DEATH5), //# Fifth Death anim + ENUM2STRING(BOTH_DEATH6), //# Sixth Death anim + ENUM2STRING(BOTH_DEATH7), //# Seventh Death anim + + ENUM2STRING(BOTH_DEATH1IDLE), //# Idle while close to death + + ENUM2STRING(BOTH_DEATHFORWARD1), //# First Death in which they get thrown forward + ENUM2STRING(BOTH_DEATHFORWARD2), //# Second Death in which they get thrown forward + ENUM2STRING(BOTH_DEATHBACKWARD1), //# First Death in which they get thrown backward + ENUM2STRING(BOTH_DEATHBACKWARD2), //# Second Death in which they get thrown backward + ENUM2STRING(BOTH_LYINGDEATH1), //# Death to play when killed lying down + ENUM2STRING(BOTH_STUMBLEDEATH1), //# Stumble forward and fall face first death + ENUM2STRING(BOTH_FALLDEATH1), //# Fall forward off a high cliff and splat death - start + ENUM2STRING(BOTH_FALLDEATH1INAIR), //# Fall forward off a high cliff and splat death - loop + ENUM2STRING(BOTH_FALLDEATH1LAND), //# Fall forward off a high cliff and splat death - hit bottom + //# DEAD POSES # Should be last frame of corresponding previous anims + ENUM2STRING(BOTH_DEAD1), //# First Death finished pose + ENUM2STRING(BOTH_DEAD2), //# Second Death finished pose + ENUM2STRING(BOTH_DEAD3), //# Third Death finished pose + ENUM2STRING(BOTH_DEAD4), //# Fourth Death finished pose + ENUM2STRING(BOTH_DEAD5), //# Fifth Death finished pose + ENUM2STRING(BOTH_DEAD6), //# Sixth Death finished pose + ENUM2STRING(BOTH_DEAD7), //# Seventh Death finished pose + ENUM2STRING(BOTH_DEADFORWARD1), //# First thrown forward death finished pose + ENUM2STRING(BOTH_DEADFORWARD2), //# Second thrown forward death finished pose + ENUM2STRING(BOTH_DEADBACKWARD1), //# First thrown backward death finished pose + ENUM2STRING(BOTH_DEADBACKWARD2), //# Second thrown backward death finished pose + ENUM2STRING(BOTH_LYINGDEAD1), //# Killed lying down death finished pose + ENUM2STRING(BOTH_STUMBLEDEAD1), //# Stumble forward death finished pose + ENUM2STRING(BOTH_FALLDEAD1LAND), //# Fall forward and splat death finished pose + //# #sep BOTH_ DEAD TWITCH/FLOP # React to being shot from death poses + ENUM2STRING(BOTH_DEAD1_FLOP), //# React to being shot from First Death finished pose + ENUM2STRING(BOTH_DEAD2_FLOP), //# React to being shot from Second Death finished pose + ENUM2STRING(BOTH_DEAD3_FLOP), //# React to being shot from Third Death finished pose + ENUM2STRING(BOTH_DEAD4_FLOP), //# React to being shot from Fourth Death finished pose + ENUM2STRING(BOTH_DEAD5_FLOP), //# React to being shot from Fifth Death finished pose + ENUM2STRING(BOTH_DEADFORWARD1_FLOP), //# React to being shot First thrown forward death finished pose + ENUM2STRING(BOTH_DEADFORWARD2_FLOP), //# React to being shot Second thrown forward death finished pose + ENUM2STRING(BOTH_DEADBACKWARD1_FLOP), //# React to being shot First thrown backward death finished pose + ENUM2STRING(BOTH_DEADBACKWARD2_FLOP), //# React to being shot Second thrown backward death finished pose + ENUM2STRING(BOTH_LYINGDEAD1_FLOP), //# React to being shot Killed lying down death finished pose + ENUM2STRING(BOTH_STUMBLEDEAD1_FLOP), //# React to being shot Stumble forward death finished pose + ENUM2STRING(BOTH_FALLDEAD1_FLOP), //# React to being shot Fall forward and splat death finished pose + + //# PAINS + ENUM2STRING(BOTH_PAIN1), //# First take pain anim + ENUM2STRING(BOTH_PAIN2), //# Second take pain anim + ENUM2STRING(BOTH_PAIN3), //# Third take pain anim + ENUM2STRING(BOTH_PAIN4), //# First take pain anim + ENUM2STRING(BOTH_PAIN5), //# Second take pain anim + ENUM2STRING(BOTH_PAIN6), //# Third take pain anim + ENUM2STRING(BOTH_PAIN7), //# First take pain anim + ENUM2STRING(BOTH_PAIN8), //# Second take pain anim + + //# ATTACKS + ENUM2STRING(BOTH_ATTACK1), //# Attack with generic 1-handed weapon + ENUM2STRING(BOTH_ATTACK2), //# Attack with generic 2-handed weapon + ENUM2STRING(BOTH_ATTACK3), //# + ENUM2STRING(BOTH_ATTACK4), //# Attack with ??? + ENUM2STRING(BOTH_ATTACK5), //# Attack with rocket launcher + + ENUM2STRING(BOTH_MELEE1), //# First melee attack + ENUM2STRING(BOTH_MELEE2), //# Second melee attack + ENUM2STRING(BOTH_MELEE3), //# Third melee attack + ENUM2STRING(BOTH_MELEE4), //# Fourth melee attack + ENUM2STRING(BOTH_MELEE5), //# Fifth melee attack + ENUM2STRING(BOTH_MELEE6), //# Sixth melee attack + + //# STANDING + ENUM2STRING(BOTH_STAND1), //# Standing idle 1 + ENUM2STRING(BOTH_STAND1_RANDOM1), //# Random standing idle + ENUM2STRING(BOTH_STAND1_RANDOM2), //# Random standing idle + ENUM2STRING(BOTH_STAND1_RANDOM3), //# Random standing idle + ENUM2STRING(BOTH_STAND1_RANDOM4), //# Random standing idle + ENUM2STRING(BOTH_STAND1_RANDOM5), //# Random standing idle + ENUM2STRING(BOTH_STAND1_RANDOM6), //# Random standing idle + ENUM2STRING(BOTH_STAND1_RANDOM7), //# Random standing idle + ENUM2STRING(BOTH_STAND1_RANDOM8), //# Random standing idle + ENUM2STRING(BOTH_STAND1_RANDOM9), //# Random standing idle + ENUM2STRING(BOTH_STAND1_RANDOM10), //# Random standing idle + ENUM2STRING(BOTH_STAND1_RANDOM11), //# Random standing idle + ENUM2STRING(BOTH_STAND1_RANDOM12), //# Random standing idle + ENUM2STRING(BOTH_STAND1_RANDOM13), //# Random standing idle + ENUM2STRING(BOTH_STAND1_RANDOM14), //# Random standing idle + ENUM2STRING(BOTH_STAND2), //# Standing idle 2 + ENUM2STRING(BOTH_STAND2_RANDOM1), //# Random standing idle + ENUM2STRING(BOTH_STAND2_RANDOM2), //# Random standing idle + ENUM2STRING(BOTH_STAND2_RANDOM3), //# Random standing idle + ENUM2STRING(BOTH_STAND2_RANDOM4), //# Random standing idle + + ENUM2STRING(BOTH_STAND3), //# Standing idle 3 + ENUM2STRING(BOTH_STAND4), //# two handed, gun down, relaxed stand + ENUM2STRING(BOTH_STAND5), //# two handed, gun up, relaxed stand + ENUM2STRING(BOTH_STAND6), //# one handed, gun at side, relaxed stand + ENUM2STRING(BOTH_STAND7), //# (Chell) timid stance while looking around slightly and breathing + ENUM2STRING(BOTH_STAND8), //# breathing after exherting oneself one handed + ENUM2STRING(BOTH_STAND9), //# breathing after exherting oneself two handed + ENUM2STRING(BOTH_STAND1TO3), //# Transition from stand1 to stand3 + ENUM2STRING(BOTH_STAND3TO1), //# Transition from stand3 to stand1 + + ENUM2STRING(BOTH_STAND2TO4), //# Transition from stand2 to stand4 + ENUM2STRING(BOTH_STAND4TO2), //# Transition from stand4 to stand2 + ENUM2STRING(BOTH_STANDTOWALK1), //# Transition from stand1 to walk1 + ENUM2STRING(BOTH_STANDTOCONSOLE1), //# a transition from stand animations to console animations + ENUM2STRING(BOTH_STANDUP1), //# standing up and stumbling + ENUM2STRING(BOTH_TALKGESTURE1), //# standing up and talking + ENUM2STRING(BOTH_TALKGESTURE2), //# standing up and talking + ENUM2STRING(BOTH_TALKGESTURE3), //# standing up and talking + + ENUM2STRING(BOTH_HELP1), //# helping hold injured4 man. + + ENUM2STRING(BOTH_LEAN1), //# leaning on a railing + ENUM2STRING(BOTH_LEAN1TODROPHELM), //# transition from LEAN1 to DROPHELM + + ENUM2STRING(BOTH_CONSOLE1), //# Using a waist-high console with both hands + ENUM2STRING(BOTH_CONSOLE1IDLE), //# Idle of CONSOLE1 + ENUM2STRING(BOTH_CONSOLE1RIGHT), //# Reach right from CONSOLE1 + ENUM2STRING(BOTH_CONSOLE1LEFT), //# Reach left from CONSOLE1 + ENUM2STRING(BOTH_CONSOLE2), //# Using a head-high wall console with the right hand + ENUM2STRING(BOTH_CONSOLE3), //# arms parallel to ground and typing similar to con.1 + ENUM2STRING(BOTH_CONSOLE3IDLE), //# arms parallel to ground and typing similar to con.1 + ENUM2STRING(BOTH_CONSOLE3RIGHT), //# arms parallel to ground and typing similar to con.1 + ENUM2STRING(BOTH_CONSOLE3LEFT), //# arms parallel to ground and typing similar to con.1 + ENUM2STRING(BOTH_CONSOLETOSTAND1), //# a transition from console animations to stand animations + + ENUM2STRING(BOTH_GUARD_LOOKAROUND1),//# Cradling weapon and looking around + ENUM2STRING(BOTH_GUARD_IDLE1), //# Cradling weapon and standing + ENUM2STRING(BOTH_GUARD_LKRT1), //# cin17, quick glance right to sound of door slamming + + ENUM2STRING(BOTH_ALERT1), //# Startled by something when on guard + ENUM2STRING(BOTH_GESTURE1), //# Generic gesture, non-specific + ENUM2STRING(BOTH_GESTURE2), //# Generic gesture, non-specific + ENUM2STRING(BOTH_GESTURE3), //# Generic gesture, non-specific + + ENUM2STRING(BOTH_CROWDLOOK1), //# Person staring out into space 1 + ENUM2STRING(BOTH_CROWDLOOK2), //# Person staring out into space 2 + ENUM2STRING(BOTH_CROWDLOOK3), //# Person staring out into space 3 + ENUM2STRING(BOTH_CROWDLOOK4), //# Person staring out into space 4 + ENUM2STRING(BOTH_GRAB1), //# Grabbing something from table + ENUM2STRING(BOTH_GRAB2), //# Grabbing something + ENUM2STRING(BOTH_GRAB3), //# Grabbing something + + ENUM2STRING(BOTH_GRABBED1), //# cin9.3 chell being grabbed 180 from munro, 28 pixels away + ENUM2STRING(BOTH_GRABBED2), //# cin9.3 idle grabbed 180 from munro, 28 pixels away + + ENUM2STRING(BOTH_SURPRISED1), //# Surprised reaction 1 + ENUM2STRING(BOTH_SURPRISED2), //# Surprised reaction 2 + ENUM2STRING(BOTH_SURPRISED3), //# Surprised reaction 3 + ENUM2STRING(BOTH_SURPRISED4), //# Surprised reaction 4 + ENUM2STRING(BOTH_SURPRISED5), //# Surprised reaction 5 + + ENUM2STRING(BOTH_SCARED1), //# Scared reaction 1 + ENUM2STRING(BOTH_SCARED2), //# Scared reaction 2 + ENUM2STRING(BOTH_CATCH1), //# Reaching to catch something falling + ENUM2STRING(BOTH_POSSESSED1), //# 7 of 9 possessed + ENUM2STRING(BOTH_POSSESSED2), //# 7 of 9 possessed with hand out + + ENUM2STRING(BOTH_SNAPTO1), //# cin.23, 7o9 coming to from borg possession + ENUM2STRING(BOTH_SNAPTO2), //# cin.23, 7o9 coming to from borg possession2 + ENUM2STRING(BOTH_DROPANGERWEAP2), //# cin.23, Nelson lowering weapon in anger + ENUM2STRING(BOTH_SHOCK1), //# telsia being zapped by electricity cinematic 9.2 + + ENUM2STRING(BOTH_PSYCHICSHOCK1), //# having visions of the boss + ENUM2STRING(BOTH_PSYCHICSHOCK2), //# having visions of the boss + ENUM2STRING(BOTH_ASSIMILATED1), //# Cin.18, Foster being assimilated by borg + ENUM2STRING(BOTH_FALSEJUMP1), //# Biessman pretending to jump down on Chell + ENUM2STRING(BOTH_LAUGH1), //# squat pose of Biessman laughing at Chell + ENUM2STRING(BOTH_LAUGH2), //# standing laugh of mocking Biessman + ENUM2STRING(BOTH_ACTIVATEBELT1), //# activating transport buffer on belt + + ENUM2STRING(BOTH_GROUNDSHAKE1), //# Bracing self when ground shakes beneath him + ENUM2STRING(BOTH_GROUNDSHAKE2), //# Falling to knees and shileding self, then standing + + ENUM2STRING(BOTH_READYWEAPON1), //# cin17, comes from greeting, just before fighting + + ENUM2STRING(BOTH_SPAWN1), //# Spawning in to the world + ENUM2STRING(BOTH_TALK1), //# Generic talk anim + + ENUM2STRING(BOTH_COVERUP1_LOOP), //# animation of getting in line of friendly fire + ENUM2STRING(BOTH_COVERUP1_START), //# transitions from stand to coverup1_loop + ENUM2STRING(BOTH_COVERUP1_END), //# transitions from coverup1_loop to stand + ENUM2STRING(BOTH_HEROSTANCE1), //# Biessman in the final shootout + ENUM2STRING(BOTH_GUILT1), //# Player has a guilty conscience after shooting a teammate. + + //# SITTING/CROUCHING + ENUM2STRING(BOTH_SIT1STAND), //# Stand up from First sitting anim + ENUM2STRING(BOTH_SIT1TO2), //# Trans from sit1 to sit2? + ENUM2STRING(BOTH_SIT1TO3), //# Trans from sit1 to sit3? + ENUM2STRING(BOTH_SIT2TO1), //# Trans from sit2 to sit1? + ENUM2STRING(BOTH_SIT2TO3), //# Trans from sit2 to sit3? + ENUM2STRING(BOTH_SIT3TO1), //# Trans from sit3 to sit1? + ENUM2STRING(BOTH_SIT3TO2), //# Trans from sit3 to sit2? + + ENUM2STRING(BOTH_SIT4TO5), //# Trans from sit4 to sit5 + ENUM2STRING(BOTH_SIT4TO6), //# Trans from sit4 to sit6 + ENUM2STRING(BOTH_SIT5TO4), //# Trans from sit5 to sit4 + ENUM2STRING(BOTH_SIT5TO6), //# Trans from sit5 to sit6 + ENUM2STRING(BOTH_SIT6TO4), //# Trans from sit6 to sit4 + ENUM2STRING(BOTH_SIT6TO5), //# Trans from sit6 to sit5 + + ENUM2STRING(BOTH_SIT7), //# sitting with arms over knees, no weapon + ENUM2STRING(BOTH_SIT7TOSTAND1), //# getting up from sit7 into stand1 + + ENUM2STRING(BOTH_TABLE_EAT1), //# Sitting at a table eating + ENUM2STRING(BOTH_TABLE_CHEW1), //# Sitting at a table chewing + ENUM2STRING(BOTH_TABLE_WIPE1), //# Sitting at a table wiping mouth + ENUM2STRING(BOTH_TABLE_DRINK1), //# Sitting at a table drinking + ENUM2STRING(BOTH_TABLE_GETUP1), //# Getting up from table + ENUM2STRING(BOTH_TABLE_DEATH1), //# Dying while sitting at a table + ENUM2STRING(BOTH_TABLE_IDLE1), //# Sitting at table breathing + ENUM2STRING(BOTH_TABLE_TALKGESTURE1), //# Sitting at table gesturing while talking + ENUM2STRING(BOTH_TABLE_GESTURE1), //# Sitting at table gesturing + ENUM2STRING(BOTH_TABLE_GESTURE2), //# Sitting at table gesturing + + ENUM2STRING(BOTH_CROUCH1), //# Transition from standing to crouch + ENUM2STRING(BOTH_CROUCH1IDLE), //# Crouching idle + ENUM2STRING(BOTH_CROUCH1WALK), //# Walking while crouched + ENUM2STRING(BOTH_UNCROUCH1), //# Transition from crouch to standing + ENUM2STRING(BOTH_CROUCH2IDLE), //# crouch and resting on back righ heel, no weapon + ENUM2STRING(BOTH_CROUCH2TOSTAND1), //# going from crouch2 to stand1 + ENUM2STRING(BOTH_GET_UP1), //# Get up from ground, face down + ENUM2STRING(BOTH_GET_UP2), //# Get up from ground, face up + + ENUM2STRING(BOTH_BENCHSIT1_IDLE), //# sitting on haz-locker room benches + ENUM2STRING(BOTH_BENCHSIT1TO2), //# Trans from benchsit1 to benchsit2 + ENUM2STRING(BOTH_BENCHSIT2TO1), //# Trans from benchsit2 to benchsit1 + ENUM2STRING(BOTH_BENCHSIT2STAND), //# Trans from benchsit to standing + ENUM2STRING(BOTH_BENCHSIT2_IDLE), //# sitting on haz-locker room benches + ENUM2STRING(BOTH_BENCHSIT1_2STAND), //# getting up to stand from sitting on haz-benches + ENUM2STRING(BOTH_BENCHSIT1_FIXBOOT),//# sitting on bench - pulling on/adjusting boot top + ENUM2STRING(BOTH_BENCHSTAND1TO2), //# transition from stand to benchstand2 + ENUM2STRING(BOTH_BENCHSTAND2), //# standing with right foot up on bench + ENUM2STRING(BOTH_BENCHSTAND2TO1), //# transition from benchstand2 to stand + + ENUM2STRING(BOTH_COUCHSIT1_IDLE), //# sitting in couch - haz lounge area + ENUM2STRING(BOTH_COUCHSIT1_TO2), //# sitting in couch - lean back to 2nd position + ENUM2STRING(BOTH_COUCHSIT1_2STAND1),//# getting up from couchsit1 to stand1 + ENUM2STRING(BOTH_COUCHSIT1_TALKGESTURE),//# sitting in couch - talking with hands + ENUM2STRING(BOTH_COUCHSIT1_GESTURELEFT),//# sitting in couch - talk gesture to the left + ENUM2STRING(BOTH_COUCHSIT1_GESTURERIGHT),//# sitting in couch - talk gesture to the right + ENUM2STRING(BOTH_KNEELHAND1), //# Jurot puts hand to Munro's face, then pulls away + ENUM2STRING(BOTH_HALT1), //# munro being grabbed by telsia before going in core room + + //# MOVING + ENUM2STRING(BOTH_WALK1), //# Normal walk + ENUM2STRING(BOTH_WALK2), //# Normal walk + ENUM2STRING(BOTH_WALK3), //# Goes with stand 3 + ENUM2STRING(BOTH_WALK4), //# Walk cycle goes to a stand4 + ENUM2STRING(BOTH_WALKTORUN1), //# transition from walk to run + + ENUM2STRING(BOTH_RUN1), //# Full run + ENUM2STRING(BOTH_RUN1START), //# Start into full run1 + ENUM2STRING(BOTH_RUN1STOP), //# Stop from full run1 + ENUM2STRING(BOTH_RUN2), //# Full run + ENUM2STRING(BOTH_RUNINJURED1), //# Run with injured left leg + ENUM2STRING(BOTH_STRAFE_LEFT1), //# Sidestep left, should loop + ENUM2STRING(BOTH_STRAFE_RIGHT1), //# Sidestep right, should loop + ENUM2STRING(BOTH_TURN_LEFT1), //# Turn left, should loop + ENUM2STRING(BOTH_TURN_RIGHT1), //# Turn right, should loop + ENUM2STRING(BOTH_RUNAWAY1), //# Runningf scared + ENUM2STRING(BOTH_SWIM1), //# Swimming + ENUM2STRING(BOTH_JUMP1), //# Jump - wind-up and leave ground + ENUM2STRING(BOTH_INAIR1), //# In air loop (from jump) + ENUM2STRING(BOTH_LAND1), //# Landing (from in air loop) + ENUM2STRING(BOTH_LAND2), //# Landing Hard (from a great height) + + ENUM2STRING(BOTH_JUMPBACK1), //# Jump - wind-up and leave ground + ENUM2STRING(BOTH_INAIRBACK1), //# In air loop (from jump) + ENUM2STRING(BOTH_LANDBACK1), //# Landing (from in air loop) + ENUM2STRING(BOTH_DIVE1), //# Dive! + ENUM2STRING(BOTH_ROLL1_LEFT), //# Roll to left side + ENUM2STRING(BOTH_ROLL1_RIGHT), //# Roll to right side + ENUM2STRING(BOTH_LADDER_UP1), //# Climbing up a ladder with rungs at 16 unit intervals + ENUM2STRING(BOTH_LADDER_DWN1), //# Climbing down a ladder with rungs at 16 unit intervals + ENUM2STRING(BOTH_LADDER_IDLE), //# Holding onto ladder + ENUM2STRING(BOTH_ONLADDER_BOT1), //# Getting on the ladder at the bottom + ENUM2STRING(BOTH_OFFLADDER_BOT1), //# Getting off the ladder at the bottom + ENUM2STRING(BOTH_ONLADDER_TOP1), //# Getting on the ladder at the top + ENUM2STRING(BOTH_OFFLADDER_TOP1), //# Getting off the ladder at the top + ENUM2STRING(BOTH_LIFT1), //# Lifting someone/thing over their shoulder + ENUM2STRING(BOTH_STEP1), //# telsia checking out lake cinematic9.2 + ENUM2STRING(BOTH_HITWALL1), //# cin.18, Kenn hit by borg into wall 56 units away + ENUM2STRING(BOTH_AMBUSHLAND1), //# landing from fall on victim + ENUM2STRING(BOTH_BIRTH1), //# birth from jumping through walls + + ENUM2STRING(BOTH_SHIELD1), //# cin.6, munro's initial reaction to explosion + ENUM2STRING(BOTH_SHIELD2), //# cin.6, munro in shielding position looping + ENUM2STRING(BOTH_WALKPUSH1), //# man pushing crate + ENUM2STRING(BOTH_PUSHTOSTAND1), //# man coming from pushing crate to stand1 + + //# FLY - IDLE + ENUM2STRING(BOTH_FLY_IDLE1), //# Idle while flying + ENUM2STRING(BOTH_FLY_IDLE2), //# Idle while flying + + //# FLY - MOVING + ENUM2STRING(BOTH_FLY_START1), //# Start flying + ENUM2STRING(BOTH_FLY_STOP1), //# Stop flying + ENUM2STRING(BOTH_FLY_LOOP1), //# Normal flying, should loop + + ENUM2STRING(BOTH_FLOAT1), //# Crew floating through space 1 + ENUM2STRING(BOTH_FLOAT2), //# Crew floating through space 2 + ENUM2STRING(BOTH_FLOATCONSOLE1), //# Crew floating and working on console + + //# LYING + ENUM2STRING(BOTH_LIE_DOWN1), + ENUM2STRING(BOTH_LIE_DOWN2), + ENUM2STRING(BOTH_LIE_DOWN3), //# reaction to local disnode being destroyed + + ENUM2STRING(BOTH_PAIN2WRITHE1), //# Transition from upright position to writhing on ground anim + ENUM2STRING(BOTH_PRONE2RLEG), //# Lying on ground reach to grab right leg + ENUM2STRING(BOTH_PRONE2LLEG), //# Lying on ground reach to grab left leg + ENUM2STRING(BOTH_WRITHING1), //# Lying on ground writhing in pain + ENUM2STRING(BOTH_WRITHING1RLEG), //# Lying on ground writhing in pain, holding right leg + ENUM2STRING(BOTH_WRITHING1LLEG), //# Lying on ground writhing in pain, holding left leg + ENUM2STRING(BOTH_WRITHING2), //# Lying on ground writhing in pain in a different way + + ENUM2STRING(BOTH_INJURED1), //# Lying down), against wall - can also be sleeping + ENUM2STRING(BOTH_INJURED2), //# Injured pose 2 + ENUM2STRING(BOTH_INJURED3), //# Injured pose 3 + ENUM2STRING(BOTH_INJURED4), //# Injured pose 4 + ENUM2STRING(BOTH_INJURED4TO5), //# Transition from INJURED4 to INJURED5 + ENUM2STRING(BOTH_INJURED5), //# Injured pose 5 + ENUM2STRING(BOTH_INJURED6), //# Injured pose 5 + ENUM2STRING(BOTH_INJURED6ATTACKSTART), //# Start attack while in injured 6 pose + ENUM2STRING(BOTH_INJURED6ATTACKSTOP), //# End attack while in injured 6 pose + + ENUM2STRING(BOTH_INJURED6COMBADGE), //# Hit combadge while in injured 6 pose + + ENUM2STRING(BOTH_INJURED6POINT), //# Chang points to door while in injured state + + ENUM2STRING(BOTH_INJUREDTOSTAND1), //# Runinjured to stand1 + + ENUM2STRING(BOTH_CRAWLBACK1), //# Lying on back), crawling backwards with elbows + ENUM2STRING(BOTH_SITWALL1), //# Sitting against a wall + ENUM2STRING(BOTH_SLEEP1), //# laying on back-rknee up-rhand on torso + ENUM2STRING(BOTH_SLEEP2), //# on floor-back against wall-arms crossed + ENUM2STRING(BOTH_SLEEP3), //# Sleeping in a chair + ENUM2STRING(BOTH_SLEEP4), //# Slumped over table + ENUM2STRING(BOTH_SLEEP5), //# Laying on side sleeping on flat sufrace + ENUM2STRING(BOTH_SLEEP1GETUP), //# alarmed and getting up out of sleep1 pose to stand + ENUM2STRING(BOTH_SLEEP1GETUP2), //# + ENUM2STRING(BOTH_SLEEP2GETUP), //# alarmed and getting up out of sleep2 pose to stand + ENUM2STRING(BOTH_SLEEP3GETUP), //# alarmed and getting up out of sleep3 pose to stand + ENUM2STRING(BOTH_SLEEP3DEATH), //# death in chair, from sleep3 idle + ENUM2STRING(BOTH_SLEEP3DEAD), //# death in chair, from sleep3 idle + + ENUM2STRING(BOTH_SLEEP_IDLE1), //# rub face and nose while asleep + ENUM2STRING(BOTH_SLEEP_IDLE2), //# shift position while asleep - stays in sleep2 + ENUM2STRING(BOTH_SLEEP_IDLE3), //# Sleep idle 3 + ENUM2STRING(BOTH_SLEEP_IDLE4), //# Sleep idle 4 + ENUM2STRING(BOTH_SLEEP1_NOSE), //# Scratch nose from SLEEP1 pose + ENUM2STRING(BOTH_SLEEP2_SHIFT), //# Shift in sleep from SLEEP2 pose + ENUM2STRING(BOTH_RESTRAINED1), //# Telsia tied to medical table + ENUM2STRING(BOTH_RESTRAINED1POINT), //# Telsia tied to medical table pointing at Munro + + ENUM2STRING(BOTH_LIFTED1), //# + ENUM2STRING(BOTH_CARRIED1), //# Fits with TORSO_CARRY1, carried over shoulder + ENUM2STRING(BOTH_CARRIED2), //# Laying over object + + + //# BORG-SPECIFIC + ENUM2STRING(BOTH_PLUGIN1), //# Borg plugs self in to alcove + ENUM2STRING(BOTH_PLUGGEDIN1), //# Last frame of Borg plug in sequence + ENUM2STRING(BOTH_PLUGOUT1), //# Borg unplugs self from alcove + //# HUNTER-SEEKER BOT-SPECIFIC + ENUM2STRING(BOTH_POWERUP1), //# Wakes up + + //================================================= + //ANIMS IN WHICH ONLY THE UPPER OBJECTS ARE IN MD3 + //================================================= + //# WEAPON-RELATED + ENUM2STRING(TORSO_DROPWEAP1), //# Put weapon away + ENUM2STRING(TORSO_DROPWEAP2), //# Put weapon away + ENUM2STRING(TORSO_DROPWEAP3), //# Put weapon away + ENUM2STRING(TORSO_RAISEWEAP1), //# Draw Weapon + ENUM2STRING(TORSO_RAISEWEAP2), //# Draw Weapon + ENUM2STRING(TORSO_RAISEWEAP3), //# Draw Weapon + ENUM2STRING(TORSO_WEAPONREADY1), //# Ready to fire 1 handed weapon + ENUM2STRING(TORSO_WEAPONREADY2), //# Ready to fire 2 handed weapon + ENUM2STRING(TORSO_WEAPONREADY3), //# Ready to fire 2 handed weapon + ENUM2STRING(TORSO_WEAPONIDLE1), //# Holding 1 handed weapon + ENUM2STRING(TORSO_WEAPONIDLE2), //# Holding 2 handed weapon + ENUM2STRING(TORSO_WEAPONIDLE3), //# Holding 2 handed weapon + //# USING NON-WEAPON OBJECTS + ENUM2STRING(TORSO_TRICORDER1), //# Using a tricorder + ENUM2STRING(TORSO_MEDICORDER1), //# Using a Medical Tricorder + ENUM2STRING(TORSO_PADD1), //# Using a PADD + + ENUM2STRING(TORSO_EQUIPMENT1), //# Twisting pipe with both hands + ENUM2STRING(TORSO_EQUIPMENT2), //# Fidgiting with cylinder with both hands + ENUM2STRING(TORSO_EQUIPMENT3), //# Using equipment one handed + ENUM2STRING(TORSO_WRIST1), //# cin.24, Chang detonating bomb with wrist device + + + //# MISC + ENUM2STRING(TORSO_COMBADGE1), //# Right hand to left breast + ENUM2STRING(TORSO_COMBADGE2), //# Left hand to left breast + ENUM2STRING(TORSO_COMBADGE3), //# Combadge touch from stand4 + + ENUM2STRING(TORSO_REDALERT1), //# Hitting comm button on wall with hand (Kirk-like) + ENUM2STRING(TORSO_HANDGESTURE1), //# gestures to left one hand + ENUM2STRING(TORSO_HANDGESTURE2), //# gestures to right one hand + ENUM2STRING(TORSO_HANDGESTURE3), //# gestures to the left both hands + ENUM2STRING(TORSO_HANDGESTURE4), //# gestures to the right both hands + ENUM2STRING(TORSO_HANDGESTURE5), //# ? + ENUM2STRING(TORSO_HANDGESTURE6), //# pointing (flank right) while talking & holding a weapon + ENUM2STRING(TORSO_HANDGESTURE7), //# pointing (forward) while talking & holding a weapon + ENUM2STRING(TORSO_HANDGESTURE8), //# pointing (flank left) while talking & holding a weapon + ENUM2STRING(TORSO_HANDGESTURE9), //# quick point right from stand 4 + ENUM2STRING(TORSO_HANDGESTURE10), //# quick point forward from stand 4 + ENUM2STRING(TORSO_HANDGESTURE11), //# quick point left from stand 4 + ENUM2STRING(TORSO_HANDGESTURE12), //# gesturing with both hands forward + ENUM2STRING(TORSO_HANDGESTURE13), //# gesturing a shrug as if not knowing answer + + ENUM2STRING(TORSO_HEADNOD1), //# nod in affirmation + ENUM2STRING(TORSO_HEADSHAKE1), //# head goes down while shaking left and right in dissapointment + + ENUM2STRING(TORSO_HYPOSPRAY1), //# man giving hypo to people + ENUM2STRING(TORSO_HYPOSPRAY4), //# using hypospray on telsia in scav5 + + ENUM2STRING(TORSO_HANDEXTEND1), //# doctor reaching for hypospray in scav5 + ENUM2STRING(TORSO_HANDRETRACT1), //# doctor taking hypospray from player in scav5 + + ENUM2STRING(TORSO_DROPHELMET1), //# Drop the helmet to the waist + ENUM2STRING(TORSO_RAISEHELMET1), //# Bring the helmet to the head + ENUM2STRING(TORSO_REACHHELMET1), //# reaching for helmet off of 60 tall cabinet + ENUM2STRING(TORSO_GRABLBACKL), //# reach to lower back with left hand + ENUM2STRING(TORSO_GRABUBACKL), //# reach to upper back with left hand + ENUM2STRING(TORSO_GRABLBACKR), //# reach to lower back with right hand + ENUM2STRING(TORSO_GRABUBACKR), //# reach to upper back with right hand + + ENUM2STRING(TORSO_STAND2TOWEAPONREADY2), //# cin.23, Nelson raising weapon in alarm and ready to fire + + ENUM2STRING(TORSO_HAND1), //# Exchanging items - giver + ENUM2STRING(TORSO_HAND2), //# Exchanging items - receiver + + ENUM2STRING(TORSO_POKERIDLE1), //# holding cards + ENUM2STRING(TORSO_POKERIDLE2), //# re-arranging cards + ENUM2STRING(TORSO_POKERIDLE3), //# put card on table + + ENUM2STRING(TORSO_SPEECHLESS1), //# hanging head in grief 1 + ENUM2STRING(TORSO_SPEECHLESS2), //# hanging head in grief 2 + ENUM2STRING(TORSO_SHOUT1), //# left hand to mouth + ENUM2STRING(TORSO_CARRY1), //# Carrying someone/thing over their shoulder (can go from BOTH_LIFT1) + + //================================================= + //ANIMS IN WHICH ONLY THE LOWER OBJECTS ARE IN MD3 + //================================================= + ENUM2STRING(LEGS_WALKBACK1), //# Walk1 backwards + ENUM2STRING(LEGS_WALKBACK2), //# Walk2 backwards + ENUM2STRING(LEGS_RUNBACK1), //# Run1 backwards + ENUM2STRING(LEGS_RUNBACK2), //# Run2 backwards + ENUM2STRING(LEGS_TURN1), //# What legs do when you turn your lower body to match your upper body facing + ENUM2STRING(LEGS_TURN2), //# Leg turning from stand2 + ENUM2STRING(LEGS_LEAN_LEFT1), //# Lean left + ENUM2STRING(LEGS_LEAN_RIGHT1), //# Lean Right + ENUM2STRING(LEGS_KNEELDOWN1), //# Get down on one knee? + ENUM2STRING(LEGS_KNEELUP1), //# Get up from one knee? + + ENUM2STRING(LEGS_CRLEAN_LEFT1), //# Crouch Lean left + ENUM2STRING(LEGS_CRLEAN_RIGHT1), //# Crouch Lean Right + //must be terminated + NULL,-1 +};*/ +//#endif //cg_players_h + +#endif diff --git a/cgame/cg_consolecmds.c b/cgame/cg_consolecmds.c new file mode 100644 index 0000000..1c13e30 --- /dev/null +++ b/cgame/cg_consolecmds.c @@ -0,0 +1,1065 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_consolecmds.c -- text commands typed in at the local console, or +// executed by a key binding + +#include "cg_local.h" + +#define emotesDEF + +static void CG_ObjectivesDown_f( void ) { + cg.showObjectives = qtrue; +} + +static void CG_ObjectivesUp_f( void ) +{ + cg.showObjectives = qfalse; +} + +void CG_TargetCommand_f( void ) { + int targetNum; + char test[4]; + + targetNum = CG_CrosshairPlayer(); + if (!targetNum ) { + return; + } + + trap_Argv( 1, test, 4 ); + trap_SendConsoleCommand( va( "gc %i %i", targetNum, atoi( test ) ) ); +} + + + +/* +================= +CG_SizeUp_f + +Keybinding command +================= +*/ +static void CG_SizeUp_f (void) { + trap_Cvar_Set("cg_viewsize", va("%i",(int)(cg_viewsize.integer+10))); +} + + +/* +================= +CG_SizeDown_f + +Keybinding command +================= +*/ +static void CG_SizeDown_f (void) { + trap_Cvar_Set("cg_viewsize", va("%i",(int)(cg_viewsize.integer-10))); +} + + +/* +============= +CG_Viewpos_f + +Debugging command to print the current view position +============= +*/ +static void CG_Viewpos_f (void) { + CG_Printf ("%s (%i %i %i) : %f\n", cgs.mapname, (int)cg.refdef.vieworg[0], + (int)cg.refdef.vieworg[1], (int)cg.refdef.vieworg[2], + (int)cg.refdefViewAngles[YAW]); +} + +/* +============= +CG_ClientPos_f + +Debugging command to print the current client position +============= +*/ +static void CG_ClientPos_f ( void ) +{ + CG_Printf( "OUTPUT: %s | ORIGIN( %f %f %f ) | ANGLES( %f %f %f )\n", cgs.mapname, + cg.snap->ps.origin[0], + cg.snap->ps.origin[1], + cg.snap->ps.origin[2], + cg.snap->ps.viewangles[0], + cg.snap->ps.viewangles[1], + cg.snap->ps.viewangles[2] ); +} + +static void CG_ScoresDown_f( void ) { + if ( cg.scoresRequestTime + 2000 < cg.time ) { + // the scores are more than two seconds out of data, + // so request new ones + cg.scoresRequestTime = cg.time; + trap_SendClientCommand( "score" ); + + // leave the current scores up if they were already + // displayed, but if this is the first hit, clear them out + if ( !cg.showScores ) { + cg.showScores = qtrue; + cg.numScores = 0; + } + } else { + // show the cached contents even if they just pressed if it + // is within two seconds + cg.showScores = qtrue; + } +} + +static void CG_ScoresUp_f( void ) { + cg.showScores = qfalse; + cg.scoreFadeTime = cg.time; +} + +static void CG_TellTarget_f( void ) { + int clientNum; + char command[128]; + char message[128]; + + clientNum = CG_CrosshairPlayer(); + if ( clientNum == -1 ) { + return; + } + + trap_Args( message, 128 ); + Com_sprintf( command, 128, "tell %i %s", clientNum, message ); + trap_SendClientCommand( command ); +} + +static void CG_TellAttacker_f( void ) { + int clientNum; + char command[128]; + char message[128]; + + clientNum = CG_LastAttacker(); + if ( clientNum == -1 ) { + return; + } + + trap_Args( message, 128 ); + Com_sprintf( command, 128, "tell %i %s", clientNum, message ); + trap_SendClientCommand( command ); +} + +/* +================= +Cmd_ShakeCamera_f +================= +*/ +/*void CG_ShakeCamera_cmd( void ) { + //vec3_t end, start, forward, up; + //gclient_t *client; + //gentity_t *other; + //gentity_t *sayA; + + //if ( ent->client->sess.sessionClass != PC_ADMIN ) { + // return; + //} + CG_CameraShake( 300, 30000 ); + //AngleVectors(ent->parent->client->ps.viewangles, forward, right, up); +}*/ + + +void CG_Cough_cmd( void ) +{ + CG_Printf(" ,. \n"); CG_Printf(" ..:, :Xt. ,:. \n"); + CG_Printf(" ,=+t: .IRX= :++=. \n"); CG_Printf(" .=iVt:. :RYYI. .itt+ \n"); + CG_Printf(" .:tXI=;. tRtiV; ,IYY:. \n"); CG_Printf(" .+;ii=;. ,XVi+Vt. :tIi+ \n"); + CG_Printf(" .;ti;;:. +RI++IY, ,+tt=. \n"); CG_Printf(" ,++YY;. ,XXi+++X= ;IYI=. \n"); + CG_Printf(" ;ttY+;. .,=iVRI++++YX+;. ;VYt; \n"); CG_Printf(" .;ii+=, .;IXRRXVi++++iVRXVi:. ,=iii. \n"); + CG_Printf(" .==;ti, .;YRRVVXYii+++++IVIVRXt, ,+=tI= \n"); CG_Printf(" .iitY=, .tRRVXXVRV+++ii++YRXVIYXV; :tYti, \n"); + CG_Printf(" .+iii=,,IBVVXYiiXViiiiiiitVtIXViVR= ,+t+I: \n"); CG_Printf(" =+=I:.tBVXVt=;tRIiiiiiiiiXi:=YXiIX; :+=It; \n"); + CG_Printf(" .;;tYt:;RVVV+=:,YRiiiiiiiiiYI,.:IXiVY..+IYi= \n"); CG_Printf(" .ti=t+;tRIXi;, :XViiiiiiiiiIV: ,YViX=.:titt. \n"); + CG_Printf(" iY++I;YVYY=: +BIiiiiiiiiiiX= +XiVi;i++Vi, \n"); CG_Printf(" ,+YYYI:VYYY;. .YRiiiiiiiiiiiVt. ;RIYt:IIVVi: \n"); + CG_Printf(" ,+tYXi;YVIX; ;RVtiiiiIXXtiiVI, iRIVt,=XVit: \n"); CG_Printf(" .+iiti++XiXI. iBIiiiiYXIIXtiIV: :XXIV++;i+iI;.\n"); + CG_Printf(" ;Ii=ii:VYtRi,VRtiiiVVi=;IXitX=;VBYXI=i+;iV+;.\n"); CG_Printf(" ;tYtVt;;XYIRXBVttiVV+;:.:VYiXVRBVXY+;+IYVt+, \n"); + CG_Printf(" =iiItii,=XVIRRIttXV+=:..,tRtVBXVRI+=i:iIit+. \n"); CG_Printf(" :t==++I:.=YXYIIiYBXYIttIVRBYtVXXI+;;t+;;+Y=, \n"); + CG_Printf(" +I=;+Y= .:IRItYIVXRRRBBRXXVIRY+=;.:i=;iVi;. \n"); CG_Printf(" .+IYVV+: +BYXXVXXXXXXXXXVRVVi;:.:;tVYY+=: \n"); + CG_Printf(" .+ttii+ .IBXY++ittIIIti++tXXi, .++=tI+;: \n"); CG_Printf(" ;YYtIY;;VBI+;:,::;;;;;:,:IBt,::tItYV=. \n"); + CG_Printf(" =IYYI++ti+;, ....... :Xt;i=iYYI+;. \n"); CG_Printf(" .:+i++ii;;. .=i=+i=t+;;:. \n"); + CG_Printf(" ,tYIVI==:,.. ..,;=+iYIVt:.. \n"); CG_Printf(" ,itt+iIYYti;. ,;itYIIt:iIi=;. \n"); + CG_Printf(" .:;;:+tIIVIi:.;iYYIii+=:,;;:. \n"); CG_Printf(" . ,:=itIXi.tXYit=;::, . \n"); + CG_Printf(" .+tti=,,iIt+;. \n"); CG_Printf(" .:;;:. ,;;;:. \n"); +} + +/* +static const char* cmdStr[][] = + { "rankList", "crewman\t - \tCrewman\n + cadet1\t - \tCadet 4th Class\n + cadet2\t - \tCadet 3rd Class\n + cadet3\t - \tCadet 2nd Class\n + cadet4\t - \tCadet 1st Class\n + ensign\t - \tEnsign\n + ltjg\t - \tLieutenant Junior Grade\n + lt\t - \tLieutenant\n + ltcmdr\t - \tLt.Commander\n + cmdr\t - \tCommander\n + capt\t - \tCaptain\n + cmmdr\t - \tCommodore\n + adm2\t - \tRear Admiral\n + adm3\t - \tVice Admiral\n + adm4\t - \tAdmiral\n + adm5\t - \tFleet Admiral\n + -=Use Page Up & Page Down to scroll=-\n" }, + { "classlist", "Command\n + Science\n + Security\n + Engineer\n + Medical\n + Alien\n" }, + { "adminlist", "admins\n + disarm_tripmine\n + drag\n + forcename\n + forcekill\n + forcekillradius\n + forceclass\n + forcerank\n + forcemodel\n + give\n + giveto\n + kicktarget\n + msg\n + n00b\n + revive\n + shake\n + undrag\n + -=Use Page Up & Page Down to scroll=-\n" };*/ + + +/*const char* cmdStr[3][2] = +{ + { "rankList", "crewman\t - \tCrewman\ncadet1\t - \tCadet 4th Class\ncadet2\t - \tCadet 3rd Class\ncadet3\t - \tCadet 2nd Class\ncadet4\t - \tCadet 1st Class\nensign\t - \tEnsign\nltjg\t - \tLieutenant Junior Grade\nlt\t - \tLieutenant\nltcmdr\t - \tLt.Commander\ncmdr\t - \tCommander\ncapt\t - \tCaptain\ncmmdr\t - \tCommodore\nadm2\t - \tRear Admiral\nadm3\t - \tVice Admiral\nadm4\t - \tAdmiral\nadm5\t - \tFleet Admiral\n-=Use Page Up & Page Down to scroll=-\n" }, + { "classlist", "Command\nScience\nSecurity\nEngineer\nMedical\nAlien\n" }, + { "adminlist", "admins\ndisarm_tripmine\ndrag\nforcename\nforcekill\nforcekillradius\nforceclass\nforcerank\nforcemodel\ngive\ngiveto\nkicktarget\nmsg\nn00b\nrevive\nshake\nundrag\n-=Use Page Up & Page Down to scroll=-\n" } +};*/ + +/* +========================= +CG_CmdList_cmd +TiM: This is to help those keep track +of those freeky huge list of things +we've put in this mod +FIX: Meh... since the majority +of these are dynamic now and need calculation +I'll put them in separate funcs. +========================= +*/ + +/*void CG_CmdList_cmd ( void ) { + char StrArg[MAX_STRING_CHARS]; + int i; + + trap_Argv( 1, StrArg, sizeof(StrArg) ); + + if ( !StrArg[0] ) { + CG_Printf( "\nUsage: Displays a list of commands and variables for easy access\nCommand: cmdList \n\nAvailable lists are:\n" ); + + for ( i = 0; i < sizeof( cmdStr ) / sizeof( cmdStr[0] ); i++ ) { + CG_Printf( "%s\n", cmdStr[i][0] ); + } + + return; + } + + for ( i = 0; i < sizeof( cmdStr ) / sizeof( cmdStr[0]); i++ ) { + if ( !Q_stricmp( StrArg, cmdStr[i][0]) ) { + CG_Printf( "\n%s\n", cmdStr[i][1] ); + return; + } + } + + CG_Printf( "ERROR: Invalid Argument. Please check for validity and spelling errors.\n" ); +}*/ + +/* +========================= +CG_RankList_cmd +TiM: Scans the rank struct, and gets +the names of all the ranks we can use ATM +========================= +*/ +void CG_RankList_cmd( void ) { + int i; + + //Print Titles + CG_Printf( S_COLOR_CYAN "RPG-X: Available Ranks\n"); + CG_Printf( S_COLOR_GREEN "Console Name \t - \t Formal Name\n" ); + + //Loop thru each val and print them + for ( i = 0; i < MAX_RANKS; i++ ) { + //if ( strlen( cgs.ranksData[i].consoleName ) < 1 || strlen( cgs.ranksData[i].formalName ) < 1 ) + // return; + + if ( cgs.ranksData[i].consoleName[0] ) + CG_Printf( "%s \t - \t %s\n", cgs.ranksData[i].consoleName, cgs.ranksData[i].formalName ); + else + break; + } +} + +/* +========================= +CG_ClassList_cmd +TiM: Scans the class struct, and gets +the names of all the ranks we can use ATM +========================= +*/ +void CG_ClassList_cmd( void ) { + int i; + + //Print Titles + CG_Printf( S_COLOR_CYAN "RPG-X: Available Classes\n"); + CG_Printf( S_COLOR_GREEN "Formal Name\n" ); + + //Loop thru each val and print them + for ( i = 0; i < MAX_CLASSES; i++ ) { + //if ( strlen( cgs.ranksData[i].consoleName ) < 1 || strlen( cgs.ranksData[i].formalName ) < 1 ) + // return; + + if ( cgs.classData[i].formalName[0] ) + CG_Printf( "%s\n", cgs.classData[i].formalName[0] ); + else + break; + } +} + +/* +========================= +CG_BeamList_cmd +TiM: Returns a list showing +the index of each target_location +ent so people know the data needed +to beam to various locations. +========================= +*/ +void CG_BeamList_cmd( void ) { + const char *locStr; + int i; + + //Print Titles + CG_Printf( S_COLOR_CYAN "RPG-X Current Beam Locations\n" ); + CG_Printf( S_COLOR_GREEN "Location Name \t - \t Location Index\n" ); + + //Based off the string data that is transmitted to the CG on Init + //Get the name and index of each location + for ( i = 1; i < MAX_LOCATIONS; i++ ) { + locStr = CG_ConfigString( CS_LOCATIONS + i ); + + if ( locStr[0] ) { + CG_Printf( "%s \t - \t%i\n", locStr, i ); + } + locStr = NULL; //reset just in case + } +} + +/* +========================= +CG_Emote_f +TiM: The first portion of the +emote system. While JKA works +by storing a copy of animations +on the server (something we cannot +replicate easily here without +destroying user freedom futhur >.<), so it +automatically knows the length of time +each anim should run for, here, we'll +hacikly override this by calculating the +run length on the client as they have the data, +and then transmitting it to the server. +Hacky, I know. + +And if other players don't have the same model, +it could potentially show animation glitches. +Although not as bad as the alternative... +========================= +*/ + +void CG_Emote_f( void ) { + const char *argStr; + emoteList_t *emote = NULL; + int i; + animation_t *anims; + int animLength; + //int animLengthUpper; + //int animLengthLower; + qboolean emoteFound=qfalse; + + argStr = CG_Argv( 1 ); + if ( !argStr[0] ) { + CG_Printf( S_COLOR_RED "ERROR: No emote specified.\n" ); + return; + } + + //TiM: Hack override for eyes shut, angry eyes and alert mode. + //No more data is needed + if ( !Q_stricmp( argStr, "eyes_shut" ) || !Q_stricmp( argStr, "eyes_frown" ) || !Q_stricmpn( argStr, "alert", 5 ) || !Q_stricmpn( argStr, "alert2", 6 ) ) + { + trap_SendClientCommand( va( "doEmote %s", argStr ) ); + return; + } + + if ( cg.predictedPlayerEntity.currentState.eFlags & EF_DEAD ) { + CG_Printf( S_COLOR_RED "ERROR: Cannot play emotes when dead.\n" ); + return; + } + + //find out emote in the list + //value of numEmotes calced in bg_misc.c + //or if an int was supplied as an arg, use that + /*if ( !argStr[0] >= '0' && argStr[0] <= '9' ) + { + i = atoi( argStr ); + + if ( i > 0 || i < bg_numEmotes ) { + emote = &bg_emoteList[i]; + emoteFound = qtrue; + } + else { + CG_Printf( S_COLOR_RED "ERROR: An invalid emote number was given.\n" ); + return; + } + } + else + {*/ + for ( i = 0; i < bg_numEmotes; i++ ) + { //i < sizeof( emoteList ) / sizeof( emoteList[0] ) + emote = &bg_emoteList[i]; + + if ( emote && !Q_stricmp( emote->name, argStr ) ) + { + emoteFound = qtrue; + break; + } + } + //} + + if ( !emoteFound ) { + CG_Printf( S_COLOR_RED "ERROR: Specified emote not found\n" ); + return; + } + + anims = &cg_animsList[cgs.clientinfo[ cg.predictedPlayerState.clientNum ].animIndex].animations[ emote->enumName ]; + + //if anim num less than 0, then this is a stub anim + if ( !anims || anims->numFrames < 0 ) { + CG_Printf( S_COLOR_RED "ERROR: Current character cannot perform that emote.\n" ); + return; + } + + //Anim length for lower model + if ( !( emote->animFlags & EMOTE_LOOP_UPPER ) && !( emote->animFlags & EMOTE_LOOP_LOWER ) ) { + //numFrames * (1000 / fps = frameLerp ) = time length + animLength = anims->numFrames * anims->frameLerp; + } + else { + animLength = -1; + } + + //send the command to the server + trap_SendClientCommand( va( "doEmote %i %i", i, animLength ) ); + + //add this emote to the emotes recently played menu + { + int j; + char* cvar; + char buffer[256]; + qboolean foundSlot=qfalse; + + for ( j = 1; j <= NUM_CVAR_STORES; j++ ) { + cvar = va( "ui_recentEmote%i", j ); + + //found a free slot + trap_Cvar_VariableStringBuffer( cvar, buffer, 256 ); + + //oh this emote's already here... no point adding it again + if ( atoi(buffer) == i ) { + foundSlot = qtrue; + break; + } + + if ( atoi(buffer) == -1 ) { + trap_Cvar_Set( cvar, va( "%i", i ) ); + foundSlot = qtrue; + break; + } + } + + //whup... no slot found. better push them all forward one + if ( !foundSlot ) { + char* cvar2; + + for ( j = 2; j <= NUM_CVAR_STORES; j++ ) { + cvar = va( "ui_recentEmote%i", j-1 ); + cvar2 = va( "ui_recentEmote%i", j ); + + trap_Cvar_VariableStringBuffer( cvar2, buffer, 256 ); + trap_Cvar_Set( cvar, va( "%i", atoi(buffer) ) ); + + if ( j == NUM_CVAR_STORES ) { + cvar = va( "ui_recentEmote%i", NUM_CVAR_STORES ); + trap_Cvar_Set( cvar, va( "%i", i ) ); + } + } + } + } +} + + +/* +========================= +CG_LocEdit_f +========================= +*/ +static fileHandle_t f; +void CG_LocEdit_f(void) { + char path[MAX_QPATH]; + char buffer[MAX_STRING_CHARS]; + const char *argptr; + int i; + + argptr = CG_Argv(1); + + if(!Q_stricmpn(argptr, "start", 5)) { + Com_sprintf(path, sizeof(path), "%s", cgs.mapname); + + COM_StripExtension(path, path); + Com_sprintf(path, sizeof(path), "%s.locations"); + + trap_FS_FOpenFile(path, &f, FS_READ); + + if(f) { + CG_Printf(S_COLOR_RED "locedit: %s.locations already exist! Skipping.\n", path); + trap_FS_FCloseFile(f); + return; + } + + trap_FS_FOpenFile(path, &f, FS_APPEND); + + if(f) { + if((argptr = CG_Argv(2)) != NULL) { + i = atoi(argptr); + if(i) { + trap_FS_Write("LocationsList2\n", 15, f); + } else { + trap_FS_Write("LocationsList\n", 14, f); + } + trap_FS_Write("{\n", 2, f); + CG_Printf(S_COLOR_YELLOW "locedit: file created...\n"); + CG_Printf(S_COLOR_YELLOW "locedit: writing file header...\n"); + } else { + CG_Printf(S_COLOR_RED "locedit: insufficent number of arguments.\n"); + trap_FS_FCloseFile(f); + return; + } + } + } else if(!Q_stricmpn(argptr, "stop", 4)) { + if(!f) { + CG_Printf(S_COLOR_RED "locedit: no locations file loaded!\n"); + return; + } + trap_FS_Write("}", 1, f); + CG_Printf(S_COLOR_YELLOW "locedit: writing file end...\n"); + trap_FS_FCloseFile(f); + CG_Printf(S_COLOR_YELLOW "locedit: closed file.\n"); + } else if(!Q_stricmpn(argptr, "add", 3)) { + if(!f) { + CG_Printf(S_COLOR_RED "locedit: no locations file loaded!\n"); + return; + } + + memset(buffer, 0, sizeof(buffer)); + + Com_sprintf(buffer, sizeof(buffer), "\t{ %f, %f, %f } { 0, %f, 0 } ", floor(cg.snap->ps.origin[0]), + floor(cg.snap->ps.origin[1]), + floor(cg.snap->ps.origin[2]), + floor(cg.snap->ps.viewangles[1] - 24.0f)); + + argptr = CG_Argv(2); + Com_sprintf(buffer, sizeof(buffer), "%s \"%s\"", buffer, argptr); + argptr = CG_Argv(3); + if(argptr) { + Com_sprintf(buffer, sizeof(buffer), "%s \"%s\"", buffer, argptr); + } + Com_sprintf(buffer, sizeof(buffer), "%s;\n", buffer); + trap_FS_Write(buffer, strlen(buffer), f); + CG_Printf(S_COLOR_YELLOW "locedit - added location: %s\n", buffer); + } else if(!Q_stricmpn(argptr, "nl", 2)) { + if(!f) { + CG_Printf(S_COLOR_RED "locedit: no locations file loaded!\n"); + return; + } + + trap_FS_Write("\n", 1, f); + CG_Printf(S_COLOR_YELLOW "locedit: added an empty line.\n"); + } +} + +/*================================================================================= +Third Person Camera View Commands +TiM : These commands activate code in the thirdperson rendering code to let players +zoom around their characters smoothly. +==================================================================================*/ + +void CG_ThirdPersonForwardDown_f ( void ) { + if ( !cg.zoomedForward ) { + cg.zoomedForward = qtrue; + } +} +void CG_ThirdPersonForwardUp_f ( void ) { + if ( cg.zoomedForward ) { + cg.zoomedForward = qfalse; + } +} + + + +void CG_ThirdPersonBackwardDown_f ( void ) { + if ( !cg.zoomedBackward ) { + cg.zoomedBackward = qtrue; + } +} +void CG_ThirdPersonBackwardUp_f ( void ) { + if ( cg.zoomedBackward ) { + cg.zoomedBackward = qfalse; + } +} + + + +void CG_ThirdPersonLeftDown_f ( void ) { + if ( !cg.zoomedLeft ) { + cg.zoomedLeft = qtrue; + } +} +void CG_ThirdPersonLeftUp_f ( void ) { + if ( cg.zoomedLeft ) { + cg.zoomedLeft = qfalse; + } +} + + + +void CG_ThirdPersonRightDown_f ( void ) { + if ( !cg.zoomedRight ) { + cg.zoomedRight = qtrue; + } +} +void CG_ThirdPersonRightUp_f ( void ) { + if ( cg.zoomedRight ) { + cg.zoomedRight = qfalse; + } +} + + + +void CG_ThirdPersonUpDown_f ( void ) { + if ( !cg.zoomedUp ) { + cg.zoomedUp = qtrue; + } +} +void CG_ThirdPersonUpUp_f ( void ) { + if ( cg.zoomedUp ) { + cg.zoomedUp = qfalse; + } +} + + + +void CG_ThirdPersonDownDown_f ( void ) { + if ( !cg.zoomedDown ) { + cg.zoomedDown = qtrue; + } +} +void CG_ThirdPersonDownUp_f ( void ) { + if ( cg.zoomedDown ) { + cg.zoomedDown = qfalse; + } +} + + + +void CG_ThirdPersonAngleLeftDown_f ( void ) { + if ( !cg.zoomAngleLeft ) { + cg.zoomAngleLeft = qtrue; + } +} +void CG_ThirdPersonAngleLeftUp_f ( void ) { + if ( cg.zoomAngleLeft ) { + cg.zoomAngleLeft = qfalse; + } +} + + + +void CG_ThirdPersonAngleRightDown_f ( void ) { + if ( !cg.zoomAngleRight ) { + cg.zoomAngleRight = qtrue; + } +} +void CG_ThirdPersonAngleRightUp_f ( void ) { + if ( cg.zoomAngleRight ) { + cg.zoomAngleRight = qfalse; + } +} + + + +void CG_ThirdPersonPitchDownDown_f ( void ) { + if ( !cg.zoomPitchDown ) { + cg.zoomPitchDown = qtrue; + } +} +void CG_ThirdPersonPitchDownUp_f ( void ) { + if ( cg.zoomPitchDown ) { + cg.zoomPitchDown = qfalse; + } +} + + + +void CG_ThirdPersonPitchUpDown_f ( void ) { + if ( !cg.zoomPitchUp ) { + cg.zoomPitchUp = qtrue; + } +} +void CG_ThirdPersonPitchUpUp_f ( void ) { + if ( cg.zoomPitchUp ) { + cg.zoomPitchUp = qfalse; + } +} + + +//------------------------------------------------------------------------------ + +const char* cVars[] = { "cg_thirdPersonRange", "cg_thirdPersonAngle", + "cg_thirdPersonVertOffset", "cg_thirdPersonHorzOffset", + "cg_thirdPersonPitchOffset" }; + +vmCvar_t* TPSVars[] = { &cg_thirdPersonRange, &cg_thirdPersonAngle, + &cg_thirdPersonVertOffset, &cg_thirdPersonHorzOffset, + &cg_thirdPersonPitchOffset }; + +//Set the third person values back to their hard-coded CVAR counter parts +//Ie revert any temporary changes. +void CG_ThirdPersonRevert_f ( void ) { + + int i; + char value[MAX_TOKEN_CHARS]; + + for (i = 0; i < 5; i++ ){ + trap_Cvar_VariableStringBuffer ( cVars[i], value, sizeof( value ) ); + TPSVars[i]->value = atof( value ); + //Q_strncpyz( TPSVars[i]->string, value, 256 ); + } +} + +//TiM : If the default values for these CVARs are changed in cg_main.c, update them here. +//I would consider linking directly to the values, but with scope, and then locating them in the +//struct array, this is way faster. +//Resets the values to the game's defaults. Useful if you screwed up the view big time. +void CG_ThirdPersonReset_f ( void ) { + int defValues[] = { 80, 0, 16, 0, 0 }; + int i; + + for (i = 0; i < 5; i++ ) { + TPSVars[i]->value = defValues[i]; + trap_Cvar_Set( cVars[i], va( "%i", defValues[i] ) ); + } +} + +//Takes the current values from all of the thirdperson pointer variables, and sets the +//hard coded CVARs to the same value, effectively making them permanent +void CG_ThirdPersonCommit_f ( void ) { + int i; + + for (i = 0; i < 5; i++ ) { + trap_Cvar_Set( cVars[i], va("%f", TPSVars[i]->value ) ); + } + CG_Printf( "Current Third Person CVAR values committed.\n" ); + //since no screen changes occur. Let the user know something happened. +} + +//Toggles between first and third person +void CG_ToggleThirdPerson_f ( void ) { + int value; + + value = !( cg_thirdPerson.integer > 0 ); //This is cool. It'll toggle the value each call. + + trap_Cvar_Set( "cg_thirdPerson", va( "%i", value ) ); +} + +//TiM - Test the ability to handle binary data streams +//void CG_LoadBinaryData( void ) +//{ +// const char *fileRoute = "rpgx.idkey"; +// fileHandle_t f; +// int len; +// byte buffer[SECURITY_SIZE]; +// rpgxSecurityFile_t *c; +// +// if (!fileRoute) +// return; +// +// len = trap_FS_FOpenFile( fileRoute, &f, FS_READ ); +// +// if ( !len ) +// return; +// +// trap_FS_Read( buffer, len, f ); +// +// trap_FS_FCloseFile( f ); +// +// c = (rpgxSecurityFile_t *)((byte *)buffer); +// +// CG_Printf( "ID: %i, Hash: %i, PID: %i\n", c->ID, c->hash > 0xFFFF ? 1 : 0, c->playerID > 0xFFFF ? 1 : 0); +// CG_Printf( "%i\n", (unsigned)atoi( sv_securityHash.string ) > 0xFFFF ? 1 : 0 ); +//} + +//================================================================================ + +typedef struct { + char *cmd; + void (*function)(void); +} consoleCommand_t; + +static consoleCommand_t commands[] = { + { "testgun", CG_TestGun_f }, + { "testmodel", CG_TestModel_f }, + { "nextframe", CG_TestModelNextFrame_f }, + { "prevframe", CG_TestModelPrevFrame_f }, + { "nextskin", CG_TestModelNextSkin_f }, + { "prevskin", CG_TestModelPrevSkin_f }, + { "viewpos", CG_Viewpos_f }, + { "+info", CG_ScoresDown_f }, + { "-info", CG_ScoresUp_f }, + { "+zoom", CG_ZoomDown_f }, + { "-zoom", CG_ZoomUp_f }, + + //TiM : Modelview code + { "+thirdPersonForward", CG_ThirdPersonForwardDown_f }, //moving the camera forward + { "-thirdPersonForward", CG_ThirdPersonForwardUp_f }, + { "+thirdPersonBackward", CG_ThirdPersonBackwardDown_f }, //moving the camera backward + { "-thirdPersonBackward", CG_ThirdPersonBackwardUp_f }, + { "+thirdPersonLeft", CG_ThirdPersonLeftDown_f }, //moving the camera left + { "-thirdPersonLeft", CG_ThirdPersonLeftUp_f }, + { "+thirdPersonRight", CG_ThirdPersonRightDown_f }, //moving the camera right + { "-thirdPersonRight", CG_ThirdPersonRightUp_f }, + { "+thirdPersonUp", CG_ThirdPersonUpDown_f }, //moving the camera up + { "-thirdPersonUp", CG_ThirdPersonUpUp_f }, + { "+thirdPersonDown", CG_ThirdPersonDownDown_f }, //moving the camera down + { "-thirdPersonDown", CG_ThirdPersonDownUp_f }, + { "+thirdPersonAngleLeft", CG_ThirdPersonAngleLeftDown_f }, //rotating the camera left + { "-thirdPersonAngleLeft", CG_ThirdPersonAngleLeftUp_f }, + { "+thirdPersonAngleRight", CG_ThirdPersonAngleRightDown_f }, //rotating the camera right + { "-thirdPersonAngleRight", CG_ThirdPersonAngleRightUp_f }, + { "+thirdPersonPitchDown", CG_ThirdPersonPitchDownDown_f }, //pitching the camera down + { "-thirdPersonPitchDown", CG_ThirdPersonPitchDownUp_f }, + { "+thirdPersonPitchUp", CG_ThirdPersonPitchUpDown_f }, //pitching the camera up + { "-thirdPersonPitchUp", CG_ThirdPersonPitchUpUp_f }, + { "thirdPersonRevert", CG_ThirdPersonRevert_f }, //revert current view to previous settings + { "thirdPersonReset", CG_ThirdPersonReset_f }, //reset values to CVAR defaults + { "thirdPersonCommit", CG_ThirdPersonCommit_f }, //set CVARs to current settings + { "thirdPerson", CG_ToggleThirdPerson_f }, //Toggle the 3rd persn CVAR + //TiM + + { "clientPos", CG_ClientPos_f }, + { "sizeup", CG_SizeUp_f }, + { "sizedown", CG_SizeDown_f }, + { "weapnext", CG_NextWeapon_f }, + { "weapprev", CG_PrevWeapon_f }, + { "weapon", CG_Weapon_f }, + { "tell_target", CG_TellTarget_f }, + { "tell_attacker", CG_TellAttacker_f }, + { "tcmd", CG_TargetCommand_f }, + { "loaddefered", CG_LoadDeferredPlayers }, // spelled wrong, but not changing for demo... + { "+analysis", CG_ObjectivesDown_f }, + { "-analysis", CG_ObjectivesUp_f }, + //{ "+shake", CG_ShakeCamera_cmd }, + { "iloverpg-x", CG_Cough_cmd }, + //{ "commandList", CG_CmdList_cmd }, + { "rankList", CG_RankList_cmd }, + { "locationList", CG_BeamList_cmd }, + { "classList", CG_ClassList_cmd }, + { "emote", CG_Emote_f }, + { "locedit", CG_LocEdit_f }, + //{ "fileID", CG_LoadBinaryData } +}; + + +/* +================= +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; + + 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 + // + // TiM: This trap command also adds the commands to the 'tab list' that users can + // use thru the console, so adding any and all game side commands (that we want the users to know about/access of course lol ;P ) + // would be a good idea too. :) + trap_AddCommand ("kill"); + trap_AddCommand ("say"); + trap_AddCommand ("say_team"); + // START MOD + trap_AddCommand ("say_class"); + //trap_AddCommand ("giveTo"); + trap_AddCommand ("forceName"); + trap_AddCommand ("forceKill"); + trap_AddCommand ("forceKillRadius"); + trap_AddCommand ("forceClass"); + trap_AddCommand ("kickTarget"); + // END MOD + trap_AddCommand ("give"); + trap_AddCommand ("god"); + trap_AddCommand ("notarget"); + trap_AddCommand ("noclip"); + trap_AddCommand ("team"); + trap_AddCommand ("class"); + trap_AddCommand ("follow"); + trap_AddCommand ("levelshot"); + trap_AddCommand ("addbot"); + trap_AddCommand ("setviewpos"); + trap_AddCommand ("vote"); + trap_AddCommand ("callvote"); + trap_AddCommand ("loaddeferred"); // spelled wrong, but not changing for demo + + //TiM - uh START MOD AGAIN + trap_AddCommand("laser"); + trap_AddCommand("flashlight"); + trap_AddCommand("cloak"); + trap_AddCommand("flight"); + trap_AddCommand("EVASuit"); + trap_AddCommand("forceName"); + trap_AddCommand("forceKill"); + trap_AddCommand("forceKillRadius"); + trap_AddCommand("shake"); + trap_AddCommand("drag"); + trap_AddCommand("undrag"); + trap_AddCommand("flushTripmines"); //disarm_tripmines + trap_AddCommand("rank"); + trap_AddCommand("forceRank"); + trap_AddCommand("forceModel"); + trap_AddCommand("forcePlayer"); + trap_AddCommand("adminLogin"); + trap_AddCommand("adminList"); + trap_AddCommand("revive"); + trap_AddCommand("n00b"); + trap_AddCommand("msg"); + trap_AddCommand("playMusic"); + trap_AddCommand("stopMusic"); + trap_AddCommand("playSound"); + trap_AddCommand("fxGun"); + trap_AddCommand("flushFX"); + trap_AddCommand("clampInfo"); + trap_AddCommand("spawnChar"); + trap_AddCommand("flushChars"); + trap_AddCommand("flushEmote"); + trap_AddCommand("beamToPlayer"); + trap_AddCommand("beamToLocation"); + + trap_AddCommand("kick2"); + trap_AddCommand("botlist"); + trap_AddCommand("addip"); + trap_AddCommand("removeip"); + trap_AddCommand("listip"); + trap_AddCommand("game_memory"); + trap_AddCommand("entitylist"); + trap_AddCommand("useEnt" ); + + trap_AddCommand("banUser"); + trap_AddCommand("findID"); + trap_AddCommand("removeID"); + + trap_AddCommand("me"); + trap_AddCommand("meLocal"); + + trap_AddCommand("mapsList"); + + //END MOD AGAIN + //TiM - May I just say. WOAH! THAT'S A LOT!! O_O! + + //START MOD AGAIN - xD + //by Marcin - 04/12/2008 + trap_AddCommand("drop"); + trap_AddCommand("flushDropped"); + //END MOD + + //START MOD ANOTHER TIME AGAIN xD + //GSIO01 | 08/05/2009 + trap_AddCommand("lock"); + trap_AddCommand("ffColor"); + trap_AddCommand("remodulate"); + trap_AddCommand("unlockAll"); + trap_AddCommand("lockAll"); + trap_AddCommand("changeFreq"); + trap_AddCommand("alert"); + trap_AddCommand("msg2"); + trap_AddCommand("forcevote"); + trap_AddCommand("listSPs"); + trap_AddCommand("getEntInfo"); + trap_AddCommand("getOrigin"); + trap_AddCommand("getEntByTargetname"); + trap_AddCommand("getEntByTarget"); + trap_AddCommand("getEntByBmodel"); + trap_AddCommand("setOrigin"); + trap_AddCommand("getBrushEntCount"); + trap_AddCommand("findEntitiesInRadius"); + trap_AddCommand("spawnTEnt"); + trap_AddCommand("flushTEnts"); + + //XPRERIMENTAL + trap_AddCommand("universalTrans"); + //END MOD + + //temp + trap_AddCommand("ui_holodeck"); + + #ifdef XTRA + trap_AddCommand("userlogin"); + trap_AddCommand("userAdd"); + trap_AddCommand("sql_setup"); + trap_AddCommand("userMod"); + trap_AddCommand("userDel"); + #endif + + #ifdef CG_LUA + trap_AddCommand("lua_status"); + #endif +} + diff --git a/cgame/cg_draw.c b/cgame/cg_draw.c new file mode 100644 index 0000000..71fa7bd --- /dev/null +++ b/cgame/cg_draw.c @@ -0,0 +1,5084 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_draw.c -- draw all of the graphical elements during +// active (after loading) gameplay + +#include "cg_local.h" +#include "cg_text.h" +#include "cg_screenfx.h" + +// set in CG_ParseTeamInfo +int sortedTeamPlayers[TEAM_MAXOVERLAY]; +int numSortedTeamPlayers; +int drawTeamOverlayModificationCount = -1; + +//TiM: dCross +//qboolean CG_WorldCoordToScreenCoord(vec3_t worldCoord, float *x, float *y, qboolean clamp); +//end dCross + +//TiM: Tricorder Parameters +vec3_t vfwd; +vec3_t vright; +vec3_t vup; +vec3_t vfwd_n; +vec3_t vright_n; +vec3_t vup_n; +int infoStringCount; + +static qboolean drawCrosshairName=qfalse; + +extern void InitPostGameMenuStruct(); + +static void CG_InterfaceStartup(); + +char *ingame_text[IGT_MAX]; // Holds pointers to ingame text + +int zoomFlashTime=0; + +/*typedef enum { + RADAR_UP, + RADAR_MIDDLE, + RADAR_DOWN +} radarType_t;*/ + +interfacegraphics_s interface_graphics[IG_MAX] = +{ +// type timer x y width height file/text graphic, min max color style ptr + SG_VAR, 0.0, 0, 0, 0, 0, NULL, 0, 0, 0, CT_NONE, 0, // IG_GROW + + SG_VAR, 0.0, 0, 0, 0, 0, NULL, 0, 0, 0, CT_NONE, 0, // IG_HEALTH_START + SG_GRAPHIC, 0.0, 5, 429, 32, 64, "gfx/interface/rpgx_healthbar_leftcorner", 0, 0, 0, CT_DKBROWN1, 0, // IG_HEALTH_BEGINCAP + SG_GRAPHIC, 0.0, 64, 429, 6, 25, "gfx/interface/ammobar", 0, 0, 0, CT_DKBROWN1, 0, // IG_HEALTH_BOX1 + SG_GRAPHIC, 0.0, 72, 429, 0, 25, "gfx/interface/ammobar", 0, 0, 0, CT_LTBROWN1, 0, // IG_HEALTH_SLIDERFULL + SG_GRAPHIC, 0.0, 0, 429, 0, 25, "gfx/interface/ammobar", 0, 0, 0, CT_DKBROWN1, 0, // IG_HEALTH_SLIDEREMPTY + SG_GRAPHIC, 0.0, 72, 429, 16, 32, "gfx/interface/rpgx_healthbar_endcap", 0, 0, 147, CT_DKBROWN1, 0, // IG_HEALTH_ENDCAP + SG_NUMBER, 0.0, 23, 425, 16, 32, NULL, 0, 0, 0, CT_LTBROWN1, NUM_FONT_BIG, // IG_HEALTH_COUNT + SG_VAR, 0.0, 0, 0, 0, 0, NULL, 0, 0, 0, CT_NONE, 0, // IG_HEALTH_END + + SG_VAR, 0.0, 0, 0, 0, 0, NULL, 0, 0, 0, CT_NONE, 0, // IG_ARMOR_START + SG_GRAPHIC, 0.0, 20, 458, 32, 16, "gfx/interface/armorcap1", 0, 0, 0, CT_DKPURPLE1, 0, // IG_ARMOR_BEGINCAP + SG_GRAPHIC, 0.0, 64, 458, 6, 12, "gfx/interface/ammobar", 0, 0, 0, CT_DKPURPLE1, 0, // IG_ARMOR_BOX1 + SG_GRAPHIC, 0.0, 72, 458, 0, 12, "gfx/interface/ammobar", 0, 0, 0, CT_LTPURPLE1, 0, // IG_ARMOR_SLIDERFULL + SG_GRAPHIC, 0.0, 0, 458, 0, 12, "gfx/interface/ammobar", 0, 0, 0, CT_DKPURPLE1, 0, // IG_ARMOR_SLIDEREMPTY + SG_GRAPHIC, 0.0, 72, 458, 16, 16, "gfx/interface/armorcap2", 0, 0, 147, CT_DKPURPLE1, 0, // IG_ARMOR_ENDCAP + SG_NUMBER, 0.0, 44, 458, 16, 16, NULL, 0, 0, 0, CT_LTPURPLE1, NUM_FONT_SMALL, // IG_ARMOR_COUNT + SG_VAR, 0.0, 0, 0, 0, 0, NULL, 0, 0, 0, CT_NONE, 0, // IG_ARMOR_END + + SG_VAR, 0.0, 0, 0, 0, 0, NULL, 0, 0, 0, CT_NONE, 0, // IG_AMMO_START + SG_GRAPHIC, 0.0, 613, 429, 32, 64, "gfx/interface/ammouppercap1", 0, 0, 0, CT_LTPURPLE2, 0, // IG_AMMO_UPPER_BEGINCAP + SG_GRAPHIC, 0.0, 607, 429, 16, 32, "gfx/interface/ammouppercap2", 0, 0, 572, CT_LTPURPLE2, 0, // IG_AMMO_UPPER_ENDCAP + SG_GRAPHIC, 0.0, 613, 458, 16, 16, "gfx/interface/ammolowercap1", 0, 0, 0, CT_LTPURPLE2, 0, // IG_AMMO_LOWER_BEGINCAP + SG_GRAPHIC, 0.0, 578, 458, 0, 12, "gfx/interface/ammobar", 0, 0, 0, CT_LTPURPLE1, 0, // IG_AMMO_SLIDERFULL + SG_GRAPHIC, 0.0, 0, 458, 0, 12, "gfx/interface/ammobar", 0, 0, 0, CT_DKPURPLE1, 0, // IG_AMMO_SLIDEREMPTY + SG_GRAPHIC, 0.0, 607, 458, 16, 16, "gfx/interface/ammolowercap2", 0, 0, 572, CT_LTPURPLE2, 0, // IG_AMMO_LOWER_ENDCAP + SG_NUMBER, 0.0, 573, 425, 16, 32, NULL, 0, 0, 0, CT_LTPURPLE1, NUM_FONT_BIG, // IG_AMMO_COUNT + SG_VAR, 0.0, 0, 0, 0, 0, NULL, 0, 0, 0, CT_NONE, 0, // IG_AMMO_END + +}; + +#define LOWEROVERLAY_Y (SCREEN_HEIGHT - ICON_SIZE - 15) + +//------------------------------------------------------ + +lensFlare_t lensFlare[MAX_LENS_FLARES]; + +lensReflec_s lensReflec[10] = +{ + //width, height, offset, positive, color, shadername, shaders placeholder + { 23, 23, 0.192, qtrue, { 0.73, 0.50, 0.23 }, "gfx/effects/flares/flare_straight", 0 }, //Brown1 //5.2 + { 9, 9, 0.37, qtrue, { 0.37, 0.58, 0.55 }, "gfx/effects/flares/flare_straight", 0 }, //Aqua1 //2.7 + { 14, 14, 0.25, qfalse, { 0.37, 0.79, 0.76 }, "gfx/effects/flares/flare_radial", 0 }, //Turquoise1 //4.0 + { 86, 86, 0.556, qfalse, { 0.73, 0.50, 0.23 }, "gfx/effects/flares/flare_inverseradial", 0 }, //BigBrownInverseRad //1.8 + { 49, 49, 0.476, qfalse, { 0.73, 0.50, 0.23 }, "gfx/effects/flares/flare_straight", 0 }, //StraightBrown2 //2.1 + { 35, 35, 0.667, qfalse, { 0.34, 0.40, 0.44 }, "gfx/effects/flares/flare_straight", 0 }, //Grey1 //1.5 + { 32, 32, 0.769, qfalse, { 0.20, 0.38, 0.62 }, "gfx/effects/flares/flare_radial", 0 }, //BlueRad //1.3 + { 122, 122, 1.1, qfalse, { 0.31, 0.65, 0.36 }, "gfx/effects/flares/flare_inverseradial", 0 }, //BigInverseGreen //0.9 + { 254, 254, 1.429, qfalse, { 1.00, 1.00, 1.00 }, "gfx/effects/flares/flare_chromadisc", 0 }, //ChromaHoop //0.7 + { 52, 52, 1.429, qtrue, { 0.40, 0.56, 0.42 }, "gfx/effects/flares/flare_inverseradial", 0 }, //Green offset //0.7 +}; + +#define HALF_SCREEN_WIDTH (SCREEN_WIDTH*0.5) +#define HALF_SCREEN_HEIGHT (SCREEN_HEIGHT*0.5) + +void CG_InitLensFlare( vec3_t worldCoord, + int w1, int h1, + vec3_t glowColor, float glowOffset, float hazeOffset, int minDist, int maxDist, + vec3_t streakColor, int streakDistMin, int streakDistMax, int streakW, int streakH, qboolean whiteStreaks, + int reflecDistMin, int reflecDistMax, qboolean reflecAnamorphic, qboolean defReflecs, + qboolean clamp, float maxAlpha, int startTime, int upTime, int holdTime, int downTime ) +{ + int i; + + //First thing's first.... I understand if you hate flares :'( + if (!cg_dynamiclensflares.value) + return; + + for (i = 0; i < MAX_LENS_FLARES; i++) { //find the next free slot + if ( !lensFlare[i].qfull ) { + //VectorCopy(worldCoord, lensFlare[i].worldCoord); + lensFlare[i].worldCoord[0] = worldCoord[0]; + lensFlare[i].worldCoord[1] = worldCoord[1]; + lensFlare[i].worldCoord[2] = worldCoord[2]; + lensFlare[i].w1 = w1; + lensFlare[i].h1 = h1; + //VectorCopy(glowColor, lensFlare[i].glowColor); + lensFlare[i].glowColor[0] = glowColor[0]; + lensFlare[i].glowColor[1] = glowColor[1]; + lensFlare[i].glowColor[2] = glowColor[2]; + lensFlare[i].glowOffset = glowOffset; + lensFlare[i].hazeOffset = hazeOffset; + lensFlare[i].minDist = minDist; + lensFlare[i].maxDist = maxDist; + //VectorCopy(streakColor, lensFlare[i].streakColor); + lensFlare[i].streakColor[0] = streakColor[0]; + lensFlare[i].streakColor[1] = streakColor[1]; + lensFlare[i].streakColor[2] = streakColor[2]; + lensFlare[i].streakDistMin = streakDistMin; + lensFlare[i].streakDistMax = streakDistMax; + lensFlare[i].streakW = streakW; + lensFlare[i].streakH = streakH; + lensFlare[i].whiteStreaks = whiteStreaks; + lensFlare[i].reflecDistMin = reflecDistMin; + lensFlare[i].reflecDistMax = reflecDistMax; + lensFlare[i].reflecAnamorphic = reflecAnamorphic; + lensFlare[i].defReflecs = defReflecs; + lensFlare[i].clamp = clamp; + lensFlare[i].maxAlpha = maxAlpha; + lensFlare[i].startTime = startTime; + lensFlare[i].upTime = upTime; + lensFlare[i].holdTime = holdTime; + lensFlare[i].downTime = downTime; + lensFlare[i].qfull = qtrue; + + break; + } + } + +} + + +/* +================= +CG_WorldCoordToScreenCoord +**Blatently plagiarised from EF SP** + +OMFG this is some damn whacky maths! +It basically takes a vector variable and somehow +correlates that to an XY value on your screen!! O_o +================= +*/ + +static qboolean CG_WorldCoordToScreenCoord(vec3_t worldCoord, float *x, float *y, qboolean clamp) +{ + int xcenter, ycenter; + vec3_t local, transformed; + vec3_t fwd; + vec3_t right; + vec3_t up; + 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 * 0.5;//gives screen coords in virtual 640x480, to be adjusted when drawn + //ycenter = 480 * 0.5;//gives screen coords in virtual 640x480, to be adjusted when drawn + xcenter = 640 >> 1; + ycenter = 480 >> 1; + + AngleVectors (cg.refdefViewAngles, fwd, right, up); + VectorSubtract (worldCoord, cg.refdef.vieworg, local); + + transformed[0] = DotProduct(local,right); + transformed[1] = DotProduct(local,up); + transformed[2] = DotProduct(local,fwd); + + // Make sure Z is not negative. + if(transformed[2] < 0.01) + { + if ( clamp ) + { + transformed[2] = 0.01f; + } + else + { + return qfalse; + } + } + // Simple convert to screen coords. + xzi = xcenter / transformed[2] * (96.0/cg.refdef.fov_x);//90 //95 + yzi = ycenter / transformed[2] * (102.0/cg.refdef.fov_y);//90 //105 + + *x = (float)(xcenter + xzi * transformed[0]); + *y = (float)(ycenter - yzi * transformed[1]); + + return qtrue; +} + + +/************************************* +CG_FlareScreenTrans - TiM + +Used to return an alpha value +based on how far the xy value is +from two boundaries (Used mainly +for when the flare exits the screen +and fades out) + +The function works by drawing an imaginary +line from the minimum point to the maximum +point. If a point is above that line, +the Y value is used to calculate the alpha, +else, the X value does. +There is a slight bit +of jerkiness if the point crosses this line, +but much less worse than what was before. :) +*************************************/ + +static float CG_FlareScreenTrans(int x, int y, int xmin, int ymin, int xmax, int ymax ) +{ + //Think about it, when the XY points are in separate quadrants of the screen, + //they're all the same values anyway, but just either negative or positive. + //Making them all positive, and working on just that set kills about 8 birds with a fricken' huge stone. >:) + int lx = abs(x); + int ly = abs(y); + int lxmin = abs(xmin); + int lymin = abs(ymin); + int lxmax = abs(xmax); + int lymax = abs(ymax); + int xDif = lxmax - lxmin; + int yDif = lymax - lymin; + float grad = ( (float)lymax/(float)lxmax ); //calc the grad as if (xmin, ymin) were the origin + + float alpha = 1.0; + + //if xy is under minimums, just make it 1 :P + if (lx < lxmin && ly < lymin ) { + return alpha; + } + + if ( ly < (lx * grad) ) {//point is running along the side bar + alpha = (float)( 1.0 - ( (float)lx - (float)lxmin ) / (float)xDif ); + //CG_Printf("SIDE BAR!!!! alpha = %f, ly = %i, lymin = %i, yDif = %i\n", alpha, ly, lymin, yDif); + } + + if ( ly > ( lx * grad) ) {//point is running along the top bar + alpha = (float)( 1.0 - ( (float)ly - (float)lymin ) / (float)yDif ); + //CG_Printf("TOP BAR!!!! alpha = %f, lx = %i, lxmin = %i, xDif = %i, xEq = %f\n", alpha, lx, lxmin, xDif, ((float)lx * grad) ); + } + + //if xy has exceeded maxes, just make it 0 :P + if ( lx >= lxmax || ly >= lymax ) + alpha = 0.0; + + //Lock it just in case something weird happened. :S + if ( alpha > 1.0 ) + alpha = 1.0; + if ( alpha < 0.0 ) + alpha = 0.0; + + return alpha; + +} + + +/* +================ +CG_CorrelateMaxMinDist + +Calcuates an alpha value +between a min and a max point +so elements can fade in or out +depending on relative distance :) +================ +*/ +static float CG_CorrelateMaxMinDist( float len, int min, int max ) { + + float alpha = 1.0; + + if ( min == max && max == 0 ) //This means it will always be off + return 0.0; + + if ( min <= 0 ) //this means that the parameter wants it to always be on + return alpha; + + alpha = /*1.0 -*/ ( len - (float)min ) / ((float)max - (float)min); //calculate the alpha + + if (alpha > 1.0 ) //Clamp it.... again + alpha = 1.0; + if (alpha < 0.0 ) + alpha = 0.0; + + return alpha; + +} + +/* +================ +CG_FadeAlpha + +Modified version of +CG_FadeColor. Only +covers alpha values now, +and also has an option +to fade in as well as out +================ +*/ +float CG_FadeAlpha( int startMsec, int totalMsec, qboolean fade_in ) { + static float alpha; + int t; + + if ( startMsec == 0 ) { + return (fade_in ? 0.0 : 1.0); + } + + t = cg.time - startMsec; + + if ( t >= totalMsec ) { + return (fade_in ? 1.0 : 0.0); + } + + // fade out + if ( totalMsec - t < FADE_TIME ) { + if (!fade_in) + alpha = ( totalMsec - t ) * 1.0/FADE_TIME; + else + alpha = 1.0 - (( totalMsec - t ) * 1.0/FADE_TIME); + } else { + alpha = fade_in ? 0.0 : 1.0; + } + + return alpha; +} + +/* +================ +CG_FlareTraceTrans + +Performs a trace between player +and origin, and if anything gets in +the way, an alpha value is generated +to make the flare fade out +================ +*/ + +static float prevFrac = 0.0; +static int fadeTime, fadeInTime; + +static qboolean CG_FlareTraceTrans ( vec3_t origin, float* alpha ) +{ + trace_t trace; + + CG_Trace( &trace, origin, NULL, NULL, cg.refdef.vieworg, -1, CONTENTS_SOLID|CONTENTS_BODY ); //Do a trace // switched start and end + + if ( fadeTime > 0 && fadeInTime == 0 ) { + *alpha = CG_FadeAlpha( fadeTime, 199, qfalse ); + if (*alpha == 0.0) + fadeTime = 0.0f; + } + + if ( fadeInTime > 0 && fadeTime == 0 ) { + *alpha = CG_FadeAlpha( fadeInTime, 199, qtrue ); + if (*alpha == 1.0) + fadeInTime = 0.0f; + } + + //fade out the flare + if (trace.fraction < 1.0 && prevFrac == 1.0 ) { + fadeTime = cg.time; + prevFrac = trace.fraction; + } + + //fade in the flare + if (trace.fraction == 1.0 && prevFrac < 1.0 ) { + fadeInTime = cg.time; + prevFrac = trace.fraction; + } + + if (fadeTime > 0 && fadeInTime > 0) { //Whoa, how did this happen??? + fadeTime = 0; //reset them both and all is good :) + fadeInTime = 0; + } + + if ( (fadeTime == 0.0 && fadeInTime == 0.0 ) && ( *alpha > 0.0 && *alpha < 1.0 ) ) //Now THIS effect was weird O_o + *alpha = 1.0; + + if (trace.fraction < 1.0 && prevFrac < 1.0 && fadeTime == 0 && fadeInTime == 0) { + prevFrac = trace.fraction; + return qfalse; + } + + return qtrue; + +} + +/************************************************************* +CG_DrawLensFlare - RPG-X : TiM +OMFG LENSFLARES R COOL!!!!! ^_^!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +Yes, I know I'm over-doing it now, coding this uber-huge +processor-intensive, totally un-necessary lensflare engine ;P + +Parameters Key: + +vec3_t worldCoord : Position in world to draw the flare +int w1, h1 : Initial (Maximum) w + h of the flare core +vec3_t glowColor : Color of the flare's glow +float glowOffset : Multiplier how much bigger the glow is than the core +float hazeOffset : Multiplier how much bigger the surrounding haze is to the core +int minDist : Minimum distance before the flare loses all brightness (Set to 0 if always normal size) +int maxDist : Maximum distance for flare's brightness +vec3_t streakColor : Color of the flare's lens reflections (if 0,0,0, then a default blue is used) +int streakDistMin : Distance at where the flare is totally transparent (Set to 0 if always on) +int streakDistMax : Distance at where the flare is totally opaque (Set to same as above to turn it always off) +int streakW : Length of the anamorphic lens streak +int streakH : Height of the anamorphic lens streak +qboolean whiteStreaks : Adds white streaks to the center of normal streaks ;P +int reflecDistMin : Distance at where the reflections are totally transparent (Set to NULL if always on) +int reflecDistMax : Distance at where the reflections are totally opaque (Set to same value as above if wanted off) +qboolean reflecAnamorphic : Enables anamorphic lens reflections +qboolean defReflecs : Makes the Lens Reflections default colors +qboolean clamp : If qtrue, the lensflare will not resize as the distance changes +float maxAlpha : All alpha values of the elements in the flare will not exceed this number +int upTime : How long it takes for the flare to go from 0 intense to maximum intense +int holdTime : How long the flare stays at max intensity for +int downTime : How long it takes for the flare to go from max intensity to 0. + +**************************************************************/ + +void CG_DrawLensFlare( lensFlare_t *flare ) +{ + int w = flare->w1; + int h = flare->h1; + float x, y, streakX, streakY; + int xCart, yCart; + int i; + vec4_t color, reflecColor, strkColor; + int xMax, yMax; + vec3_t distDif, black = {0.0, 0.0, 0.0}; + int maxTime = flare->upTime + flare->holdTime + flare->downTime; + int tMaxTime = maxTime + flare->startTime; + int tUpTime = flare->upTime + flare->startTime; + int tHoldTime = flare->upTime + flare->holdTime + flare->startTime; + int tDownTime = flare->upTime + flare->holdTime + flare->downTime + flare->startTime; + float length; + + float reflecAlpha = 1.0; //alpha channel of reflections + float streakAlpha = 1.0; //alpha channel of streaks + float boundAlpha = 1.0; //alpha if flare leaves screen + float commonAlpha = 1.0; //alpha variables common too all elements + float hazeAlpha = 1.0; + + static float fadeAlpha; //This can't have a default value otherwise it screws up the flare fade transition + static float timeAlpha; //Alpha/w/h over the specified time + + //First thing's first.... I understand if you hate flares :'( + if (!cg_dynamiclensflares.value) + return; + + //if we can't get an XY value, screw it :P + if ( !CG_WorldCoordToScreenCoord( flare->worldCoord, &x, &y, qfalse) ) + return; + + //if we can't actually see the flare in line of sight, screw it again. :P + if( !CG_FlareTraceTrans( flare->worldCoord, &fadeAlpha) ) + return; + + if (maxTime > 0 && cg.time <= tMaxTime) { + + if ( cg.time <= tUpTime ) + timeAlpha = (float)(cg.time - flare->startTime) * (float)(1.0/(float)flare->upTime); + + if (cg.time <= tHoldTime && cg.time > tUpTime ) + timeAlpha = 1.0; + + if (cg.time <= tDownTime && cg.time > tHoldTime ) + timeAlpha = 1.0 - ( (float)(cg.time - flare->startTime) * (float)(1.0/(float)flare->downTime) ); + } + + if (maxTime == 0 ) + timeAlpha = 1.0; + + w = w * timeAlpha; + h = h * timeAlpha; + + //calc the distance between the player and the flare + VectorSubtract( flare->worldCoord, cg.refdef.vieworg, distDif ); + length = VectorNormalize( distDif ); + + //if the clamp boolean is false, resize the flare over player distance from it + if ( !flare->clamp ) { + w = w * CG_CorrelateMaxMinDist(length, flare->minDist, flare->maxDist ); //Change size/height in relation to distance + h = h * CG_CorrelateMaxMinDist(length, flare->minDist, flare->maxDist ); + } + + xCart = (int)(x - HALF_SCREEN_WIDTH ); //Re-orient the EF drawing engine so co-ord (0,0) is in the middle of the screen) + yCart = (int)(y - HALF_SCREEN_HEIGHT ); + + streakX = (xCart - (flare->streakW*0.5)) + HALF_SCREEN_WIDTH; //Calculate X value of lens streak based on flare position + streakY = (yCart - (flare->streakH*0.5)) + HALF_SCREEN_HEIGHT; //Calculate Y value of lens streak based on flare position + + xMax = (w*0.5) + HALF_SCREEN_WIDTH; //define the point the flare should fully fade out + yMax = (h*0.5) + HALF_SCREEN_HEIGHT; + + if ( boundAlpha > 0.0 ) { //Calculate the reflections' opacity in contrast to the edge of the screen + boundAlpha = CG_FlareScreenTrans( xCart, yCart, HALF_SCREEN_WIDTH, HALF_SCREEN_HEIGHT, xMax, yMax); + } + + //set up all of the elements with their various alphas :P + commonAlpha = commonAlpha * fadeAlpha * boundAlpha * flare->maxAlpha; + + if (commonAlpha * timeAlpha < 0.01 ) //no point in drawing if it's really really faint + return; + + reflecAlpha = reflecAlpha * commonAlpha * timeAlpha * CG_CorrelateMaxMinDist(length, flare->reflecDistMin, flare->reflecDistMax ); + streakAlpha = streakAlpha * commonAlpha * timeAlpha * CG_CorrelateMaxMinDist(length, flare->streakDistMin, flare->streakDistMax ); + hazeAlpha = hazeAlpha * commonAlpha; + + //Copy in the color the user wants, but we need control of the alpha. + VectorCopy( flare->glowColor, color ); + color[3] = hazeAlpha; + + if ( VectorCompare( flare->streakColor, black) ) { //If they specified no streakcolor, use this awesome default blue one :) + strkColor[0] = 0.31; + strkColor[1] = 0.45; + strkColor[2] = 1.0; + strkColor[3] = streakAlpha; + } + else { //else, use the color they wanted + VectorCopy( flare->streakColor, strkColor ); + strkColor[3] = streakAlpha; + } + + //Lens Reflections - those cool circly bits that go in the opposite direction of the flare + if ( reflecAlpha != 0.0 ) {//Sheez, only do this if we really WANT it O_o + for( i = 0; i < 10; i++ ) { + + //if they wanted the cool photoshoppy style reflections + if ( flare->defReflecs ) { + VectorCopy( lensReflec[i].color, reflecColor ); + reflecColor[3] = reflecAlpha; + } + else { //otherwise, just use the color they picked + VectorCopy( color, reflecColor ); + reflecColor[3] = reflecAlpha; + } + + trap_R_SetColor( reflecColor ); + + CG_DrawPic( + ( ( ( lensReflec[i].positive ? xCart : -xCart ) * lensReflec[i].offset ) + HALF_SCREEN_WIDTH ) - ( flare->reflecAnamorphic ? lensReflec[i].width : lensReflec[i].width*0.5 ), //X + ( ( ( lensReflec[i].positive ? yCart : -yCart ) * lensReflec[i].offset ) + HALF_SCREEN_HEIGHT ) - ( lensReflec[i].height*0.5 ), //Y + flare->reflecAnamorphic ? lensReflec[i].width * 2 : lensReflec[i].width, //W + lensReflec[i].height, //H + lensReflec[i].graphic //pic + ); + } + } + + //Colored Middle + Streaks + trap_R_SetColor( color ); + if ( color[3] > 0.0 ) { + x = ( xCart - ( (w*flare->hazeOffset) *0.5) + HALF_SCREEN_WIDTH ); + y = ( yCart - ( (h*flare->hazeOffset) *0.5) + HALF_SCREEN_HEIGHT ); + CG_DrawPic( x, y, w*flare->hazeOffset, h*flare->hazeOffset, cgs.media.flareHaze ); //Surrounding ambient haze + } + + trap_R_SetColor( strkColor ); + if ( strkColor[3] > 0.0f ) + CG_DrawPic( streakX , streakY , flare->streakW, flare->streakH, cgs.media.flareStreak ); //Colored portion of the anamorphic streaks + + trap_R_SetColor( color ); + if ( color[3] > 0.0f ) { + x = ( xCart - ( (w*flare->glowOffset) *0.5) + HALF_SCREEN_WIDTH ); + y = ( yCart - ( (h*flare->glowOffset) *0.5) + HALF_SCREEN_HEIGHT ); + CG_DrawPic( x, y, w*flare->glowOffset, h*flare->glowOffset, cgs.media.flareCore ); //Main colored glow bit of the main flare + } + + if ( flare->whiteStreaks ) { //if player wanted white streaks in their streaks + strkColor[0] = strkColor[1] = strkColor[2] = 1.0; + + trap_R_SetColor( strkColor ); //White + if ( strkColor[3] > 0.0 ) + CG_DrawPic( streakX + (flare->streakW*0.2), streakY + (flare->streakH*0.2), flare->streakW*0.6, flare->streakH*0.6, cgs.media.flareStreak ); //White Core of streak is ALWAYS 20% smaller. + } + + color[0] = color[1] = color [2] = 1.0f; + color[3] = hazeAlpha; + + trap_R_SetColor( color ); + if ( color[3] > 0.0 ) { + x = ( xCart - (w *0.5) + HALF_SCREEN_WIDTH ); + y = ( yCart - (h *0.5) + HALF_SCREEN_HEIGHT ); + CG_DrawPic( x, y, w, h, cgs.media.flareCore ); //Draw teh main fl4r3 :) + } + //CG_Printf("worldCoord = %f, colorAlpha = %f, streakAlpha = %f, streakColor = %f \n", flare->worldCoord[0], color[3], strkColor[3], strkColor[2]); +} + +/* +============== +CG_DrawField + +Draws large numbers for status bar and powerups +============== +*/ +/* +static void CG_DrawField (int x, int y, int width, int value) +{ + char num[16], *ptr; + int l; + int frame; + + if ( width < 1 ) + { + return; + } + + // draw number string + if ( width > 5 ) + { + width = 5; + } + + switch ( width ) + { + case 1: + value = value > 9 ? 9 : value; + value = value < 0 ? 0 : value; + break; + case 2: + value = value > 99 ? 99 : value; + value = value < -9 ? -9 : value; + break; + case 3: + value = value > 999 ? 999 : value; + value = value < -99 ? -99 : value; + break; + case 4: + value = value > 9999 ? 9999 : value; + value = value < -999 ? -999 : value; + break; + } + + Com_sprintf (num, sizeof(num), "%i", value); + l = strlen(num); + if (l > width) + l = width; + x += 2 + CHAR_WIDTH*(width - l); + + ptr = num; + while (*ptr && l) + { + if (*ptr == '-') + frame = STAT_MINUS; + else + frame = *ptr -'0'; + + CG_DrawPic( x,y, CHAR_WIDTH, CHAR_HEIGHT, cgs.media.numberShaders[frame] ); + x += CHAR_WIDTH; + ptr++; + l--; + } +} +*/ + +/* +================ +CG_Draw3DModel + +================ +*/ +static void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, qhandle_t skin, qhandle_t shader, vec3_t origin, vec3_t angles ) { + refdef_t refdef; + refEntity_t ent; + + if ( !cg_draw3dIcons.integer || !cg_drawIcons.integer ) { + return; + } + + CG_AdjustFrom640( &x, &y, &w, &h ); + + memset( &refdef, 0, sizeof( refdef ) ); + + memset( &ent, 0, sizeof( ent ) ); + AnglesToAxis( angles, ent.axis ); + VectorCopy( origin, ent.origin ); + ent.hModel = model; + ent.customSkin = skin; + ent.customShader = shader; + ent.renderfx = RF_NOSHADOW; // no stencil shadows + + refdef.rdflags = RDF_NOWORLDMODEL; + + AxisClear( refdef.viewaxis ); + + refdef.fov_x = 30; + refdef.fov_y = 30; + + refdef.x = x; + refdef.y = y; + refdef.width = w; + refdef.height = h; + + refdef.time = cg.time; + + trap_R_ClearScene(); + trap_R_AddRefEntityToScene( &ent ); + trap_R_RenderScene( &refdef ); +} + +/* +================ +CG_DrawHead + +Used for both the status bar and the scoreboard +================ +*/ + +//extern qhandle_t CG_CurrentHeadSkin( centity_t* cent, clientInfo_t* ci ); + +void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t headAngles ) { + clipHandle_t cm; + centity_t *cent; + clientInfo_t *ci; + playerState_t *ps; + float value; + float len; + vec3_t origin; + vec3_t mins, maxs; + + cent = &cg_entities[ clientNum ]; + ci = &cgs.clientinfo[ clientNum ]; + + ps = &cg.snap->ps; + + value = ps->stats[STAT_HEALTH]; + + if ( cg_draw3dIcons.integer && (ci->headOffset[0] != 404) ) { + cm = ci->headModel; + if ( !cm ) { + return; + } + + // offset the origin y and z to center the head + trap_R_ModelBounds( cm, mins, maxs ); + + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + + // calculate distance so the head nearly fills the box + // assume heads are taller than wide + len = 0.7 * ( maxs[2] - mins[2] ); + origin[0] = len / 0.268; // len / tan( fov/2 ) + + // allow per-model tweaking + VectorAdd( origin, ci->headOffset, origin ); + + CG_Draw3DModel( x, y, w, h, ci->headModel, ci->headSkin, 0, origin, headAngles ); + + if ((value < 82.000000) && (value >= 65.000000)){ + CG_Draw3DModel( x, y, w, h, ci->headModel, ci->headSkin, 0, origin, headAngles ); + }else if(value >= 49.000000){ + CG_Draw3DModel( x, y, w, h, ci->headModel, ci->headSkin, 0, origin, headAngles ); + }else if(value >= 32.000000){ + CG_Draw3DModel( x, y, w, h, ci->headModel, ci->headSkin, 0, origin, headAngles ); + }else if(value >= 2.000000){ + CG_Draw3DModel( x, y, w, h, ci->headModel, ci->headSkin, 0, origin, headAngles ); + }else if(value <= 1.000000){ + CG_Draw3DModel( x, y, w, h, ci->headModel, ci->headSkin, 0, origin, headAngles ); + } + } else if ( cg_drawIcons.integer ) { + CG_DrawPic( x, y, w, h, ci->modelIcon ); + } + + //if ( cgs.clientinfo[clientNum].health <= 1 ) {//if eliminated, draw the cross-out + // CG_DrawPic( x, y, w, h, cgs.media.eliminatedShader ); + //} else if ( ci->deferred ) {// if they are deferred, draw a cross out +// CG_DrawPic( x, y, w, h, cgs.media.deferShader ); + //} +} + +/* +================ +CG_DrawFlagModel + +Used for both the status bar and the scoreboard +================ +*/ +void CG_DrawFlagModel( float x, float y, float w, float h, int team ) { + qhandle_t cm; + float len; + vec3_t origin, angles; + vec3_t mins, maxs; + + if ( cg_draw3dIcons.integer ) { + + VectorClear( angles ); + + cm = cgs.media.redFlagModel; + + // offset the origin y and z to center the flag + trap_R_ModelBounds( cm, mins, maxs ); + + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + + // calculate distance so the flag nearly fills the box + // assume heads are taller than wide + len = 0.5 * ( maxs[2] - mins[2] ); + origin[0] = len / 0.268; // len / tan( fov/2 ) + + angles[YAW] = 60 * sin( cg.time / 2000.0 );; + + CG_Draw3DModel( x, y, w, h, + team == TEAM_RED ? cgs.media.redFlagModel : cgs.media.blueFlagModel, 0, + team == TEAM_RED ? cgs.media.redFlagShader[3] : cgs.media.blueFlagShader[3], origin, angles ); + } else if ( cg_drawIcons.integer ) { + //gitem_t *item = BG_FindItemForPowerup( team == TEAM_RED ? PW_REDFLAG : PW_BORG_ADAPT ); + + /*if (item) + { + CG_DrawPic( x, y, w, h, cg_items[ ITEM_INDEX(item) ].icon ); + }*/ + } +} + +/* +================ +CG_DrawStatusBarHead +RPG-X | Phenix | 09/06/2005 +I dont know who commented this out but it's going back in ;) +================ +*/ +static int CG_DrawStatusBarHead( float x ) { + vec3_t angles; + float size, stretch; + float frac; + + VectorClear( angles ); + + if ( cg.damageTime && cg.time - cg.damageTime < DAMAGE_TIME ) { + frac = (float)(cg.time - cg.damageTime ) / DAMAGE_TIME; + size = ICON_SIZE * 1.25 * ( 1.5 - frac * 0.5 ); + + stretch = size - ICON_SIZE * 1.25; + // kick in the direction of damage + x -= stretch * 0.5 + cg.damageX * stretch * 0.5; + + cg.headStartYaw = 180 + cg.damageX * 45; + + cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); + cg.headEndPitch = 5 * cos( crandom()*M_PI ); + + cg.headStartTime = cg.time; + cg.headEndTime = cg.time + 100 + random() * 2000; + } else { + if ( cg.time >= cg.headEndTime ) { + // select a new head angle + cg.headStartYaw = cg.headEndYaw; + cg.headStartPitch = cg.headEndPitch; + cg.headStartTime = cg.headEndTime; + cg.headEndTime = cg.time + 100 + random() * 2000; + + cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); + cg.headEndPitch = 5 * cos( crandom()*M_PI ); + } + + size = ICON_SIZE * 1.25; + } + + size = size * 3; + + // if the server was frozen for a while we may have a bad head start time + if ( cg.headStartTime > cg.time ) { + cg.headStartTime = cg.time; + } + + frac = ( cg.time - cg.headStartTime ) / (float)( cg.headEndTime - cg.headStartTime ); + frac = frac * frac * ( 3 - 2 * frac ); + angles[YAW] = cg.headStartYaw + ( cg.headEndYaw - cg.headStartYaw ) * frac; + angles[PITCH] = cg.headStartPitch + ( cg.headEndPitch - cg.headStartPitch ) * frac; + + CG_DrawHead( x, 480 - (size + BIGCHAR_HEIGHT + 5), size, size, + cg.snap->ps.clientNum, angles ); + + return size; +} + +/* +================ +CG_DrawTeamBackground + +================ +*/ +void CG_DrawTeamBackground( int x, int y, int w, int h, float alpha, int team, qboolean scoreboard ) +{ + vec4_t hcolor; + + hcolor[3] = alpha; + if ( team == TEAM_RED ) + { + hcolor[0] = 1; + hcolor[1] = 0; + hcolor[2] = 0; + } + else if ( team == TEAM_BLUE ) + { + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 1; + } + else + { + return; // no team + } + + trap_R_SetColor( hcolor ); + CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar ); + trap_R_SetColor( NULL ); +} + +/* +================ +CG_DrawAmmo + +================ +*/ +static void CG_DrawAmmo(centity_t *cent) +{ + float value; +// float xLength; + playerState_t *ps; +// int max,brightColor_i,darkColor_i,numColor_i; + + ps = &cg.snap->ps; + + value = ps->ammo[cent->currentState.weapon]; + + return; +} + +//RPG-X: - RedTechie NO ARMOR! how many times do i have to say it! +/* +================ +CG_DrawArmor + +================ +*/ +/* +static void CG_DrawArmor(centity_t *cent) +{ + int max; + float value,xLength; + playerState_t *ps; + int lengthMax; + + ps = &cg.snap->ps; + + value = ps->stats[STAT_ARMOR]; + + interface_graphics[IG_ARMOR_COUNT].max = value; + + if (interface_graphics[IG_ARMOR_COUNT].max <= ps->stats[STAT_MAX_HEALTH]) + { + interface_graphics[IG_ARMOR_COUNT].color = CT_LTPURPLE1; // + interface_graphics[IG_ARMOR_SLIDERFULL].color = CT_LTPURPLE1; // + interface_graphics[IG_ARMOR_COUNT].style &= ~UI_PULSE; // Numbers + } + else + { + interface_graphics[IG_ARMOR_COUNT].color = CT_LTGREY; // Numbers + interface_graphics[IG_ARMOR_SLIDERFULL].color = CT_LTGREY; // + interface_graphics[IG_ARMOR_COUNT].style |= UI_PULSE; // Numbers + } + + + +// if (cg.oldarmor < value) +// { +// cg.oldArmorTime = cg.time + 100; +// } + +// cg.oldarmor = value; + +// if (cg.oldArmorTime < cg.time) +// { +// interface_graphics[IG_ARMOR_COUNT].color = CT_LTPURPLE1; // Numbers +// } +// else +// { +// interface_graphics[IG_ARMOR_COUNT].color = CT_YELLOW; // Numbers +// } + + + max = ps->stats[STAT_MAX_HEALTH]; + lengthMax = 73; + if (max > 0) + { + if (value > max) + { + xLength = lengthMax; + } + else + { + xLength = lengthMax * (value/max); + } + + } + else + { + max = 0; + xLength = 0; + } + + // Armor empty section + interface_graphics[IG_ARMOR_SLIDEREMPTY].x = 72 + xLength; + interface_graphics[IG_ARMOR_SLIDEREMPTY].width = lengthMax - xLength; + + // Armor full section + interface_graphics[IG_ARMOR_SLIDERFULL].width = xLength; + + CG_PrintInterfaceGraphics(IG_ARMOR_START + 1,IG_ARMOR_END); + +} +*/ + +//RPG-X: - RedTechie Close but no cigar we need 3 stage health not a bar +/* +================ +CG_DrawHealth + +================ +*/ + +/*static void CG_DrawHealth(centity_t *cent) +{ + int max; + float value,xLength; + playerState_t *ps; + int lengthMax; + + ps = &cg.snap->ps; + + value = ps->stats[STAT_HEALTH]; + + + // Changing colors on numbers +// if (cg.oldhealth < value) +// { +// cg.oldHealthTime = cg.time + 100; +// } +// cg.oldhealth = value; + + // Is health changing? +// if (cg.oldHealthTime < cg.time) +// { +// interface_graphics[IG_HEALTH_COUNT].color = CT_LTBROWN1; // Numbers +// } +// else +// { +// } + interface_graphics[IG_HEALTH_COUNT].max = value; + + if (interface_graphics[IG_HEALTH_COUNT].max <= ps->stats[STAT_MAX_HEALTH]) + { + interface_graphics[IG_HEALTH_COUNT].color = CT_LTBROWN1; // + interface_graphics[IG_HEALTH_SLIDERFULL].color = CT_LTBROWN1; // + interface_graphics[IG_HEALTH_SLIDEREMPTY].color = CT_DKBROWN1; // + interface_graphics[IG_HEALTH_COUNT].style &= ~UI_PULSE; // Numbers + } + else + { + interface_graphics[IG_HEALTH_COUNT].color = CT_LTGREY; // Numbers + interface_graphics[IG_HEALTH_SLIDERFULL].color = CT_LTGREY; // + interface_graphics[IG_HEALTH_COUNT].style |= UI_PULSE; // Numbers + } + + // Calculating size of health bar + max = ps->stats[STAT_MAX_HEALTH]; + lengthMax = 73; + if (max > 0) + { + if (value < max) + { + xLength = lengthMax * (value/max); + } + else // So the graphic doesn't extend past the cap + { + xLength = lengthMax; + } + } + else + { + max = 0; + xLength = 0; + } + + // Health empty section + interface_graphics[IG_HEALTH_SLIDEREMPTY].x = 72 + xLength; + interface_graphics[IG_HEALTH_SLIDEREMPTY].width = lengthMax - xLength; + + // Health full section + interface_graphics[IG_HEALTH_SLIDERFULL].width = xLength; + + // Print it + CG_PrintInterfaceGraphics(IG_HEALTH_START + 1,IG_HEALTH_END); +}*/ + +//RPG-X: - RedTechie This is more like it +/* +================ +CG_DrawHealth +New Draw health function by yours truly RedTechie +New version by TiM lol +================ +*/ + +//static int CG_DrawHealth( centity_t *cent ) +//{ +// float value; +// float offset; +// float yOffset; +// +// value = cg.snap->ps.stats[STAT_HEALTH]; +// +// //Draw static graphics first +// CG_FillRect( 8, 428, 89, 1, colorTable[CT_LTPURPLE1] ); +// CG_FillRect( 8, 429, 1, 44, colorTable[CT_LTPURPLE1] ); +// CG_FillRect( 8, 473, 89, 1, colorTable[CT_LTPURPLE1] ); +// CG_FillRect( 96, 429, 1, 44, colorTable[CT_LTPURPLE1] ); +// +// //Okay... we'll need to work out some funky math here later... +// //For now, let's just test +// offset = (( (float)(cg.time % 2000) / 2000.0f ) * (1.0f+(1.0f-(value/100.0f)) * 0.25f)); +// yOffset = (value / 100.0f ) * 21.0f ; +// //CG_Printf( "%f\n", offset ); +// +// trap_R_SetColor( NULL ); +// CG_DrawStretchPic( 9, 450 - yOffset, 87, yOffset * 2.0f, 0.0f + offset, 0.0f, (1.0f+(1.0f-(value/100.0f)) * 0.25f) + offset, 1.0f, cgs.media.healthSineWave ); +// //CG_DrawStretchPic( 16, 413, 123, 60, 1.0f, 1.0f, 2.0f, 2.0f, cgs.media.healthSineWave ); +// +// return 125; +//} + +static int CG_DrawHealth(centity_t *cent) +{ + float value; + playerState_t *ps; + char *health_str = NULL; + int health_barwidth; + vec_t *health_txtcolor = NULL; + int health_txteffect = 0; + int x, y; + + ps = &cg.snap->ps; + + value = ps->stats[STAT_HEALTH]; + + //RPG-X: RedTechie - The GROSS math part icky icky! + if(value >= 82.000000){ + health_str = ingame_text[IGT_SB_HEALTHSTATUS1]; //RPG-X and SFEF + health_txtcolor = colorTable[CT_DKPURPLE2]; + health_txteffect = UI_BIGFONT; + }else if(value >= 65.000000){ + health_str = ingame_text[IGT_SB_HEALTHSTATUS2]; //Scott Carter, after being locked in a room with rpg-x team for 20 minuts.. + health_txtcolor = colorTable[CT_DKPURPLE2]; + health_txteffect = UI_BIGFONT; + }else if(value >= 49.000000){ + health_str = ingame_text[IGT_SB_HEALTHSTATUS3]; //Results after 10 minutes + health_txtcolor = colorTable[CT_LTBLUE2]; + health_txteffect = UI_BIGFONT; + }else if(value >= 32.000000){ + health_str = ingame_text[IGT_SB_HEALTHSTATUS4]; //Results after 15 minutes + health_txtcolor = colorTable[CT_LTBLUE2]; + health_txteffect = UI_BIGFONT; + }else if(value >= 2.000000){ + health_str = ingame_text[IGT_SB_HEALTHSTATUS5]; //Results after 20 minutes + health_txtcolor = colorTable[CT_VDKBLUE2]; + health_txteffect = UI_BIGFONT; + }else if(value <= 1.000000){ + health_str = ingame_text[IGT_SB_HEALTHSTATUS6]; //Final result - post your comments here coders ;) - + //What do you mean final result - this is like after 30 seconds in a room with the rpg-x team :P (Phenix) + health_txtcolor = colorTable[CT_RED]; //More like 10 -TiM + health_txteffect = UI_BIGFONT; + } + //Get a accurate width + health_barwidth = UI_ProportionalStringWidth(health_str,UI_BIGFONT); + + //Doom Style Health! + if (doomHead.integer == 1) + { + health_barwidth = CG_DrawStatusBarHead( 2 ); + health_barwidth = ((health_barwidth / 2) + 2) - (UI_ProportionalStringWidth(health_str,UI_BIGFONT) / 2); + UI_DrawProportionalString(health_barwidth, 460 - BIGCHAR_HEIGHT, health_str, health_txteffect, health_txtcolor); + + return health_barwidth; + } else { + x = 3; + y = 435; + + //Draw the text + UI_DrawProportionalString(x + 46, y + 11, health_str, health_txteffect, health_txtcolor); + + //RPG-X: - RedTechie The Graphics :) + + trap_R_SetColor( colorTable[CT_DKBLUE1] ); + CG_DrawPic( x, y, 85, 22, cgs.media.healthbigcurve ); //RPG-X: Big Curve //x,y,w,h=32 + CG_DrawPic( x + 49 + health_barwidth, y, 8, 7, cgs.media.healthendcap ); //RPG-X: Top End Cap - 133 + CG_DrawPic( x + 49 + health_barwidth, y + 37, 8, 7, cgs.media.healthendcap ); //RPG-X: Bottum End Cap - 133 //CG_DrawPic( x + 49 + health_barwidth, y + 49, 16, 16, cgs.media.healthendcap ); + CG_FillRect( x, y + 15, 40, 20, colorTable[CT_DKBLUE1]); //Extra bit to fill in the gap under the curve graphic + + CG_FillRect( x, y + 37, 40, 7, colorTable[CT_DKGOLD1]); //RPG-X: Middle bar //15 + CG_FillRect( x + 42, y + 37, 5+health_barwidth, 7, colorTable[CT_DKBLUE1]); //RPG-X: Bottum Horizontal bar - CG_FillRect( 45, 469, 86+health_barwidth, 15, colorTable[CT_DKBLUE1]); + CG_FillRect( x + 47, y, health_barwidth, 7, colorTable[CT_DKBLUE1]); //RPG-X: Top Horizontal bar - CG_FillRect( 61, 420, 70+health_barwidth, 15, colorTable[CT_DKBLUE1]); + + //RPG-X: RedTechie - Some eye candy text + UI_DrawProportionalString( x +40-3, y + 23, ingame_text[IGT_SB_HEALTHBARLCARS], UI_TINYFONT|UI_RIGHT, colorTable[CT_BLACK]);//456 +//x + 12 + return health_barwidth + 82; + } +} + +/* +================ +CG_DrawStatusBar + +================ +*/ +static void CG_DrawStatusBar( void ) +{ + centity_t *cent; + playerState_t *ps; + vec3_t angles; + int y=0; + vec4_t whiteA; + int x, z, i, h, yZ; + vec3_t tmpVec, eAngle, forward, dAngle; + //RPG-X: Redtechie - for the HACK code below + //int rpg_shakemycamera; + int healthBarWidth; + //float rpg_shakemycamera_intensity; + //const char *info; + + static float colors[4][4] = + { + { 1, 0.69, 0, 1.0 } , // normal + { 1.0, 0.2, 0.2, 1.0 }, // low health + {0.5, 0.5, 0.5, 1}, // weapon firing + { 1, 1, 1, 1 } }; // health > 100 + + whiteA[0] = whiteA[1] = whiteA[2] = 1.0f; whiteA[3] = 0.3f; + + cent = &cg_entities[cg.snap->ps.clientNum]; + + //RPG-X: RedTechie - HACK HACK HACK!!!! this needs to be called soon to check to shake the players cameras + /*info = CG_ConfigString( CS_SERVERINFO ); + rpg_shakemycamera = atoi( Info_ValueForKey( info, "rpg_servershakeallclients" ) ); + rpg_shakemycamera_intensity = atof( Info_ValueForKey( info, "rpg_servershakeallclientsintensity" ) ); + if(rpg_shakemycamera == 1){ + CG_CameraShake(rpg_shakemycamera_intensity,300); + }*/ + + if ( cg_drawStatus.integer == 0 ) { + return; + } + + // draw the team background + CG_DrawTeamBackground( 0, 420, 640, 60, 0.33, cg.snap->ps.persistant[PERS_TEAM], qfalse ); + + ps = &cg.snap->ps; + + VectorClear( angles ); + + // draw any 3D icons first, so the changes back to 2D are minimized + y = (SCREEN_HEIGHT - (4*ICON_SIZE) - 20); + /*if (cg.predictedPlayerState.powerups[PW_REDFLAG]) + { //fixme: move to powerup renderer? make it pulse? + // CG_FillRect( 5, y, ICON_SIZE*2, ICON_SIZE*2, whiteA); + CG_DrawFlagModel( 5, y, ICON_SIZE*2, ICON_SIZE*2, TEAM_RED ); + }*/ + /*else if (cg.predictedPlayerState.powerups[PW_BORG_ADAPT]) + { + // CG_FillRect( 5, y, ICON_SIZE*2, ICON_SIZE*2, whiteA); + // CG_DrawFlagModel( 5, y, ICON_SIZE*2, ICON_SIZE*2, TEAM_BLUE ); + //RPG-X | GSIO01 | 08/05/2009: we have flag in rpg? haha + }*/ + + // Do start + if (!cg.interfaceStartupDone) + { + CG_InterfaceStartup(); + } + + // + // ammo + // + if ( cent->currentState.weapon ) + { + CG_DrawAmmo(cent); + } + + + // + // health + // + //RPG-X | Phenix | 09/06/2005 + // Added return of the width for the cloak etc messages + healthBarWidth = CG_DrawHealth(cent); + + + // RPG-X + // Print RPG Flags + //By: RedTechie & Phenix + // + if(cg.predictedPlayerState.powerups[PW_EVOSUIT] || cg.predictedPlayerState.powerups[PW_FLIGHT] || cg.predictedPlayerState.powerups[PW_INVIS]){ + //RPG-X | Phenix | 08/06/2005 + yZ = 478 - SMALLCHAR_HEIGHT; + // UI_BIGFONT + //DEBUG + if(cg.predictedPlayerState.powerups[PW_EVOSUIT]) { + UI_DrawProportionalString(healthBarWidth, yZ, ingame_text[IGT_SB_EVOSUITSTATUS], UI_SMALLFONT, colorTable[CT_CYAN]); + yZ -= SMALLCHAR_HEIGHT + 2; + } + if(cg.predictedPlayerState.powerups[PW_INVIS]){ + UI_DrawProportionalString(healthBarWidth, yZ, ingame_text[IGT_SB_CLOAKSTATUS], UI_SMALLFONT, colorTable[CT_RED]); + yZ -= SMALLCHAR_HEIGHT + 2; + } + if(cg.predictedPlayerState.powerups[PW_FLIGHT]){ + UI_DrawProportionalString(healthBarWidth, yZ, ingame_text[IGT_SB_FLIGHTSTATUS], UI_SMALLFONT, colorTable[CT_RED]); + yZ -= SMALLCHAR_HEIGHT + 2; + } + } + + // + // armor + // + //RPG-X: - Redtechie IT A FRICKEN RP NOOOO ARMOR! OMG! + //CG_DrawArmor(cent); + + // Radar + // By Sam "-=Jazz=-"Dickinson + // http://www.telefragged.com/jazz + if ( ( cg.snap->ps.weapon == WP_TRICORDER || cg.snap->ps.weapon == WP_COMPRESSION_RIFLE ) && cg_drawradar.integer != 0 && !cg.zoomed ) + { + vec4_t radColor; + + CG_DrawPic(40, 100, 100, 100, cgs.media.radarShader); + for (i = 0; i < cg.snap->numEntities; i++) // Go through all entities in VIS range + { + if ( cg.snap->entities[i].eType == ET_PLAYER ) // If the Entity is a Player + { + // Calculate How Far Away They Are + x = (cg.snap->entities[i].pos.trBase[0] - cg.predictedPlayerState.origin[0]); + y = (cg.snap->entities[i].pos.trBase[1] - cg.predictedPlayerState.origin[1]); + z = (cg.snap->entities[i].pos.trBase[2] - cg.predictedPlayerState.origin[2]); + tmpVec[0] = x; + tmpVec[1] = y; + tmpVec[2] = 0.0; + + // Convert Vector to Angle + vectoangles(tmpVec, eAngle); + h = sqrt((x*x) + (y*y)); // Get Range + + // We only Want "YAW" value + dAngle[0] = 0.0; + dAngle[1] = AngleSubtract(eAngle[1] - 180, cg.predictedPlayerState.viewangles[1]) + 180; + dAngle[0] = 0.0; + + // Convert Angle back to Vector + AngleVectors(dAngle, forward, NULL, NULL); + VectorScale(forward, h/32, forward); +// if (h/32 < 100 && h/32 > 0) // Limit Radar Range +// { + // Draw up arrow if above, down if below, or an ordinary blip if level + // With tolerance of +- 5 units + //RPG-X: RedTechie - No teams in a RP + /*if ( cgs.gametype >= GT_TEAM ) + { + if ( cgs.clientinfo[cg.snap->entities[i].number].team == TEAM_BLUE ) + { + if (z > 64) + { + CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_blue_up); + } + else if (z < -64) + { + CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_blue_down); + } + else + { + CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_blue_level); + } + } + else if ( cgs.clientinfo[cg.snap->entities[i].number].team == TEAM_RED ) + { + if (z > 64) + { + CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_red_up); + } + else if (z < -64) + { + CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_red_down); + } + else + { + CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_red_level); + } + } + }*/ + //RPG-X: RedTechie - If Dead show them as a medical symbol + //.number + if (h/32 < 100 && h/32 > 0) { // Limit Radar Range + if ( cg_entities[cg.snap->entities[i].number].currentState.eFlags & EF_DEAD ) + { + if (z > 64) + { + CG_DrawStretchPic( 86 - forward[1], 146 - forward[0], 16, 8, 0, 0, 1, 0.5, cgs.media.rd_injured_level ); + //CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_injured_up); + } + else if (z < -64) + { + //CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_injured_down); + CG_DrawStretchPic( 86 - forward[1], 146 - forward[0], 16, 8, 0, 0.5, 1, 1, cgs.media.rd_injured_level ); + } + else + { + CG_DrawPic(86 - forward[1], 146 - forward[0], 16, 16, cgs.media.rd_injured_level); + } + } + else + { + //if ( cgs.clientinfo[cg.snap->entities[i].number].pClass == PC_COMMAND ) + //{ + // /*if (z > 64) + // { + // CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_red_up); + // } + // else if (z < -64) + // { + // CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_red_down); + // } + // else + // { + // CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_red_level); + // }*/ + // //trap_R_SetColor( colorTable[CT_RED] ); + // VectorCopy( colorTable[CT_RED], radColor ); + // radColor[3] = colorTable[CT_RED][3]; + //} + //else if ( cgs.clientinfo[cg.snap->entities[i].number].pClass == PC_SCIENCE ) + //{ + // /*if (z > 64) + // { + // CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_teal_up); + // } + // else if (z < -64) + // { + // CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_teal_down); + // } + // else + // { + // CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_teal_level); + // }*/ + // //trap_R_SetColor( colorTable[CT_TEAL] ); + // VectorCopy( colorTable[CT_TEAL], radColor ); + // radColor[3] = colorTable[CT_TEAL][3]; + //} + //else if ( cgs.clientinfo[cg.snap->entities[i].number].pClass == PC_SECURITY ) + //{ + // /*if (z > 64) + // { + // CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_blue_up); + // } + // else if (z < -64) + // { + // CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_blue_down); + // } + // else + // { + // CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_blue_level); + // }*/ + // //trap_R_SetColor( colorTable[CT_GOLD] ); + // VectorCopy( colorTable[CT_GOLD], radColor ); + // radColor[3] = colorTable[CT_GOLD][3]; + //} + //else if ( cgs.clientinfo[cg.snap->entities[i].number].pClass == PC_MEDICAL ) + //{ + // /*if (z > 64) + // { + // CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_white_up); + // } + // else if (z < -64) + // { + // CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_white_down); + // } + // else + // { + // CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_white_level); + // }*/ + // //trap_R_SetColor( colorTable[CT_TEAL] ); + // VectorCopy( colorTable[CT_TEAL], radColor ); + // radColor[3] = colorTable[CT_TEAL][3]; + //} + //else if ( cgs.clientinfo[cg.snap->entities[i].number].pClass == PC_ENGINEER ) + //{ + // /*if (z > 64) + // { + // CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_up); + // } + // else if (z < -64) + // { + // CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_down); + // } + // else + // { + // CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_level); + // }*/ + // //trap_R_SetColor( colorTable[CT_GOLD] ); + // VectorCopy( colorTable[CT_GOLD], radColor ); + // radColor[3] = colorTable[CT_GOLD][3]; + //} + //else if ( cgs.clientinfo[cg.snap->entities[i].number].pClass == PC_ALPHAOMEGA22 ) + //{ + // /*if (z > 64) + // { + // CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_teal_up); + // } + // else if (z < -64) + // { + // CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_teal_down); + // } + // else + // { + // CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_teal_level); + // }*/ + // //trap_R_SetColor( colorTable[CT_GREEN] ); + // VectorCopy( colorTable[CT_GREEN], radColor ); + // radColor[3] = colorTable[CT_GREEN][3]; + //} + //else if ( cgs.clientinfo[cg.snap->entities[i].number].pClass == PC_ADMIN && cg.snap->ps.persistant[PERS_CLASS] != PC_ADMIN ) + //{ + // //RPG-X: RedTechie - Dont show admins on radar unless you are a admin + //} + if ( cgs.clientinfo[cg.snap->entities[i].number].pClass >= 0 ) + { + radColor[0] = (float)cgs.classData[cgs.clientinfo[cg.snap->entities[i].number].pClass].radarColor[0] / 255.0f; + radColor[1] = (float)cgs.classData[cgs.clientinfo[cg.snap->entities[i].number].pClass].radarColor[1] / 255.0f; + radColor[2] = (float)cgs.classData[cgs.clientinfo[cg.snap->entities[i].number].pClass].radarColor[2] / 255.0f; + radColor[3] = 1.0f; + } + else + { + + /*if (z > 64) + { + CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_black_up); + } + else if (z < -64) + { + CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_black_down); + } + else + { + CG_DrawPic(86 - forward[1], 146 - forward[0], 7, 7, cgs.media.rd_black_level); + }*/ + //trap_R_SetColor( colorTable[CT_BLACK] ); + VectorCopy( colorTable[CT_BLACK], radColor ); + radColor[3] = colorTable[CT_BLACK][3]; + } + + if ( cgs.clientinfo[cg.snap->entities[i].number].isAdmin && !cgs.clientinfo[cg.snap->ps.clientNum].isAdmin ) + continue; + + if ( z > 64 ) + { + trap_R_SetColor( radColor ); + CG_DrawStretchPic( 86 - forward[1], 146 - forward[0], 8, 4, 0, 0, 1, 0.5, cgs.media.radarMain ); + } + else if ( z < -64 ) + { + trap_R_SetColor( radColor ); + CG_DrawStretchPic( 86 - forward[1], 146 - forward[0], 8, 4, 0, 0.5, 1, 1, cgs.media.radarMain ); + } + else + { + trap_R_SetColor( radColor ); + CG_DrawPic( 86 - forward[1], 146 - forward[0], 8, 8, cgs.media.radarMain ); + } + trap_R_SetColor( NULL ); + } + } + } + } + } + // End Radar +} + +/* +================ +CG_InterfaceStartup +================ +*/ +static void CG_InterfaceStartup() +{ + + // Turn on Health Graphics + if ((interface_graphics[IG_HEALTH_START].timer < cg.time) && (interface_graphics[IG_HEALTH_BEGINCAP].type == SG_OFF)) + { + trap_S_StartLocalSound( cgs.media.interfaceSnd1, CHAN_LOCAL_SOUND ); + + interface_graphics[IG_HEALTH_BEGINCAP].type = SG_GRAPHIC; + interface_graphics[IG_HEALTH_BOX1].type = SG_GRAPHIC; + interface_graphics[IG_HEALTH_ENDCAP].type = SG_GRAPHIC; + } + + // Turn on Armor Graphics + //RPG-X: - RedTechie how many times do i have to say NO ARMOR IN RP's! + /*if ((interface_graphics[IG_ARMOR_START].timer < cg.time) && (interface_graphics[IG_ARMOR_BEGINCAP].type == SG_OFF)) + { + if (interface_graphics[IG_ARMOR_BEGINCAP].type == SG_OFF) + { + trap_S_StartLocalSound( cgs.media.interfaceSnd1, CHAN_LOCAL_SOUND ); + } + + interface_graphics[IG_ARMOR_BEGINCAP].type = SG_GRAPHIC; + interface_graphics[IG_ARMOR_BOX1].type = SG_GRAPHIC; + interface_graphics[IG_ARMOR_ENDCAP].type = SG_GRAPHIC; + + }*/ + + // Turn on Ammo Graphics + if (interface_graphics[IG_AMMO_START].timer < cg.time) + { + if (interface_graphics[IG_AMMO_UPPER_BEGINCAP].type == SG_OFF) + { + trap_S_StartLocalSound( cgs.media.interfaceSnd1, CHAN_LOCAL_SOUND ); + interface_graphics[IG_GROW].type = SG_VAR; + interface_graphics[IG_GROW].timer = cg.time; + } + + interface_graphics[IG_AMMO_UPPER_BEGINCAP].type = SG_GRAPHIC; + interface_graphics[IG_AMMO_UPPER_ENDCAP].type = SG_GRAPHIC; + interface_graphics[IG_AMMO_LOWER_BEGINCAP].type = SG_GRAPHIC; + interface_graphics[IG_AMMO_LOWER_ENDCAP].type = SG_GRAPHIC; + } + + if (interface_graphics[IG_GROW].type == SG_VAR) + { + interface_graphics[IG_HEALTH_ENDCAP].x += 2; + interface_graphics[IG_ARMOR_ENDCAP].x += 2; + interface_graphics[IG_AMMO_UPPER_ENDCAP].x -= 1; + interface_graphics[IG_AMMO_LOWER_ENDCAP].x -= 1; + + if (interface_graphics[IG_HEALTH_ENDCAP].x >= interface_graphics[IG_HEALTH_ENDCAP].max) + { + interface_graphics[IG_HEALTH_ENDCAP].x = interface_graphics[IG_HEALTH_ENDCAP].max; + interface_graphics[IG_ARMOR_ENDCAP].x = interface_graphics[IG_ARMOR_ENDCAP].max; + + interface_graphics[IG_AMMO_UPPER_ENDCAP].x = interface_graphics[IG_AMMO_UPPER_ENDCAP].max; + interface_graphics[IG_AMMO_LOWER_ENDCAP].x = interface_graphics[IG_AMMO_LOWER_ENDCAP].max; + interface_graphics[IG_GROW].type = SG_OFF; + + interface_graphics[IG_HEALTH_SLIDERFULL].type = SG_GRAPHIC; + interface_graphics[IG_HEALTH_SLIDEREMPTY].type = SG_GRAPHIC; + interface_graphics[IG_HEALTH_COUNT].type = SG_NUMBER; + + interface_graphics[IG_ARMOR_SLIDERFULL].type = SG_GRAPHIC; + interface_graphics[IG_ARMOR_SLIDEREMPTY].type = SG_GRAPHIC; + interface_graphics[IG_ARMOR_COUNT].type = SG_NUMBER; + + interface_graphics[IG_AMMO_SLIDERFULL].type = SG_GRAPHIC; + interface_graphics[IG_AMMO_SLIDEREMPTY].type = SG_GRAPHIC; + interface_graphics[IG_AMMO_COUNT].type = SG_NUMBER; + + trap_S_StartLocalSound( cgs.media.interfaceSnd1, CHAN_LOCAL_SOUND ); + cg.interfaceStartupDone = 1; // All done + } + + interface_graphics[IG_GROW].timer = cg.time + 10; + } + + cg.interfaceStartupTime = cg.time; + + // kef -- init struct for post game awards + InitPostGameMenuStruct(); +} + +/* +=========================================================================================== + + UPPER RIGHT CORNER + +=========================================================================================== +*/ + +/* +================ +CG_DrawAttacker + +================ +*/ +static float CG_DrawAttacker( float y ) { + int t; + float size; + vec3_t angles; + const char *info; + const char *name; + int clientNum; + + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + return y; + } + + if ( !cg.attackerTime ) { + return y; + } + + clientNum = cg.predictedPlayerState.persistant[PERS_ATTACKER]; + if ( clientNum < 0 || clientNum >= MAX_CLIENTS || clientNum == cg.snap->ps.clientNum ) { + return y; + } + + t = cg.time - cg.attackerTime; + if ( t > ATTACKER_HEAD_TIME ) { + cg.attackerTime = 0; + return y; + } + + size = ICON_SIZE * 1.25; + + angles[PITCH] = 0; + angles[YAW] = 180; + angles[ROLL] = 0; + CG_DrawHead( 640 - size, y, size, size, clientNum, angles ); + + info = CG_ConfigString( CS_PLAYERS + clientNum ); + name = Info_ValueForKey( info, "n" ); + y += size; +// CG_DrawBigString( 640 - ( Q_PrintStrlen( name ) * BIGCHAR_WIDTH), y, name, 0.5 ); + UI_DrawProportionalString( 635, y, name, UI_RIGHT | UI_SMALLFONT, colorTable[CT_LTGOLD1] ); + + return y + BIGCHAR_HEIGHT + 2; +} + +/* +================== +CG_DrawSnapshot +================== +*/ +static float CG_DrawSnapshot( float y ) { + char *s; + int w; + + s = va( "time:%i frametime:%i snap:%i cmd:%i", cg.snap->serverTime, cg.frametime, + cg.latestSnapshotNum, cgs.serverCommandSequence ); + + //y = (BIGCHAR_HEIGHT * 2) + 20; + + + w = UI_ProportionalStringWidth(s,UI_BIGFONT); + + if ( cg_lagometer.integer && ( y < (BIGCHAR_HEIGHT * 2) + 20) ) { + w = w + 52; + } + + UI_DrawProportionalString(635 - (w - 2), y + 2, s, UI_BIGFONT, colorTable[CT_LTGOLD1]); + + return y + BIGCHAR_HEIGHT + 10; +} + +/* +================== +CG_DrawFPS +================== +*/ +#define FPS_FRAMES 4 +static float CG_DrawFPS( float y ) { + char *s; + int w; + static int previousTimes[FPS_FRAMES]; + static int index; + int i, total; + int fps; + static int previous; + int t, frameTime; + + // don't use serverTime, because that will be drifting to + // correct for internet lag changes, timescales, timedemos, etc + t = trap_Milliseconds(); + frameTime = t - previous; + previous = t; + + previousTimes[index % FPS_FRAMES] = frameTime; + index++; + if ( index > FPS_FRAMES ) { + // average multiple frames together to smooth changes out a bit + total = 0; + for ( i = 0 ; i < FPS_FRAMES ; i++ ) { + total += previousTimes[i]; + } + if ( !total ) { + total = 1; + } + fps = 1000 * FPS_FRAMES / total; + + s = va( "%ifps", fps ); + w = UI_ProportionalStringWidth(s,UI_BIGFONT); + //RPG-X | Phenix | 08/06/2005 + // Changed "- w" to "- (w + 50)" to account for lagometer + if ( !cg_lagometer.integer ) { + w = w - 52; + } + UI_DrawProportionalString(635 - (w + 52), y + 2, s, UI_BIGFONT, colorTable[CT_LTGOLD1]); + } + + return y + BIGCHAR_HEIGHT + 10; +} + +/* +================= +CG_DrawTimer +================= +*/ +static float CG_DrawTimer( float y ) { + char *s; + int w; + int mins, seconds, tens; + int msec; + + msec = cg.time - cgs.levelStartTime; + + seconds = msec / 1000; + mins = seconds / 60; + seconds -= mins * 60; + tens = seconds / 10; + seconds -= tens * 10; + + s = va( "%i:%i%i", mins, tens, seconds ); + + w = UI_ProportionalStringWidth(s,UI_BIGFONT); + // RPG-X | Phenix | 08/06/2005 + // Changed "- w" to "- (w + 50)" to account for lagometer + if ( !cg_lagometer.integer ) { + w = w - 52; + } + UI_DrawProportionalString(635 - (w + 52), y + 2, s, UI_BIGFONT, colorTable[CT_LTGOLD1]); + + return y + BIGCHAR_HEIGHT + 10; +} +#define TINYPAD 1.25 + +/* +================= +CG_DrawTeamOverlay +================= +*/ + +#define TEAM_OVERLAY_MAXNAME_WIDTH 12 +#define TEAM_OVERLAY_MAXLOCATION_WIDTH 16 + +static float CG_DrawTeamOverlay( float y, qboolean right, qboolean upper ) { + int x, w, h, xx; + int i, j, len; + const char *p; + vec4_t hcolor; + int pwidth, lwidth; + int plyrs; + char st[16]; + clientInfo_t *ci; + int ret_y; + + if ( !cg_drawTeamOverlay.integer ) + { + return y; + } + + if ( cg.snap->ps.persistant[PERS_TEAM] != TEAM_RED && cg.snap->ps.persistant[PERS_TEAM] != TEAM_BLUE ) + { + return y; // Not on any team + } + + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) + { + return y; + } + + plyrs = 0; + w = 0; + + // max player name width + pwidth = 0; + for (i = 0; i < numSortedTeamPlayers; i++) { + ci = cgs.clientinfo + sortedTeamPlayers[i]; + if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { + plyrs++; + len = CG_DrawStrlen(ci->name); + + if (len > pwidth) + pwidth = len; + if ( ci->pClass >= 0 /*PC_NOCLASS*/ )//if any one of them has a class, then we alloc space for the icon + w = 1; + } + } + + if (!plyrs) + return y; + + if (pwidth > TEAM_OVERLAY_MAXNAME_WIDTH) + pwidth = TEAM_OVERLAY_MAXNAME_WIDTH; + + // max location name width + lwidth = 0; + for (i = 1; i < MAX_LOCATIONS; i++) { + p = CG_ConfigString(CS_LOCATIONS + i); + if (p && *p) { + len = CG_DrawStrlen(p); + if (len > lwidth) + lwidth = len; + } + } + + if (lwidth > TEAM_OVERLAY_MAXLOCATION_WIDTH) + lwidth = TEAM_OVERLAY_MAXLOCATION_WIDTH; + + w += (pwidth + lwidth + 4); + w *= (TINYCHAR_WIDTH * TINYPAD); + + if ( right ) + x = 640 - w; + else + x = 0; + + h = plyrs * (TINYCHAR_HEIGHT * TINYPAD); + + if ( upper ) { + ret_y = y + h; + } else { + y -= h; + ret_y = y; + } + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { + hcolor[0] = 1; + hcolor[1] = 0; + hcolor[2] = 0; + hcolor[3] = 0.33; + } else { // if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 1; + hcolor[3] = 0.33; + } + trap_R_SetColor( hcolor ); + CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar ); + trap_R_SetColor( NULL ); + + for (i = 0; i < numSortedTeamPlayers; i++) { + ci = cgs.clientinfo + sortedTeamPlayers[i]; + if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { + + xx = x + TINYCHAR_WIDTH; +//Draw class icon if appropriate + if ( ci->pClass >= 0/*PC_NOCLASS*/ ) + { + //qhandle_t icon; + + //Special hack: if it's Borg who has regen going, must be Borg queen + /*if ( ci->pClass == PC_BORG && (ci->powerups&(1<pClass]; + } + CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, icon );*/ + + xx += (TINYCHAR_WIDTH * TINYPAD); + } +//draw name +// CG_DrawStringExt( xx, y, +// ci->name, hcolor, qfalse, qfalse, +// TINYCHAR_WIDTH, TINYCHAR_HEIGHT, TEAM_OVERLAY_MAXNAME_WIDTH); + hcolor[0] = hcolor[1] = hcolor[2] = hcolor[3] = 1.0; + UI_DrawProportionalString( xx, y, ci->name, UI_TINYFONT, hcolor); + + if (lwidth) { + p = CG_ConfigString(CS_LOCATIONS + ci->location); + if (!p || !*p) + p = "unknown"; + len = CG_DrawStrlen(p); + if (len > lwidth) + len = lwidth; + +// xx = x + TINYCHAR_WIDTH * 2 + TINYCHAR_WIDTH * pwidth + +// ((lwidth/2 - len/2) * TINYCHAR_WIDTH); + xx = x + TINYCHAR_WIDTH * 2 + TINYCHAR_WIDTH * pwidth; +// CG_DrawStringExt( xx, y, +// p, hcolor, qfalse, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, +// TEAM_OVERLAY_MAXLOCATION_WIDTH); + UI_DrawProportionalString( xx, y, p, UI_TINYFONT, hcolor); + + } + + CG_GetColorForHealth( ci->health, ci->armor, hcolor ); + + Com_sprintf (st, sizeof(st), "%3i %3i", ci->health, ci->armor); + + xx = x + TINYCHAR_WIDTH * 3 + + TINYCHAR_WIDTH * pwidth + TINYCHAR_WIDTH * lwidth; + +// CG_DrawStringExt( xx, y, +// st, hcolor, qfalse, qfalse, +// TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 ); + UI_DrawProportionalString( xx, y, st, UI_TINYFONT, hcolor); + + // draw weapon icon + xx += (TINYCHAR_WIDTH * TINYPAD) * 3; + + if ( cg_weapons[ci->curWeapon].weaponIcon ) { + CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, + cg_weapons[ci->curWeapon].weaponIcon ); + } else { + CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, + cgs.media.deferShader ); + } + + // Draw powerup icons + if (right) { + xx = x; + } else { + xx = x + w - TINYCHAR_WIDTH; + } + for (j = 0; j < PW_NUM_POWERUPS; j++) { + if (ci->powerups & (1 << j)) { + gitem_t *item = BG_FindItemForPowerup( j ); + + if (item) + { + CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, + trap_R_RegisterShader( item->icon ) ); + } + if (right) { + xx -= (TINYCHAR_WIDTH * TINYPAD); + } else { + xx += (TINYCHAR_WIDTH * TINYPAD); + } + } + } + + y += (TINYCHAR_HEIGHT * TINYPAD); + } + } + + return ret_y; +} + + +/* +===================== +CG_DrawUpperRight + +===================== +*/ +static void CG_DrawUpperRight( void ) { + float y; + + //vec3_t origin = {960, -1214, 242 }; + //vec3_t color = { 0.6, 0.6, 1.0 }; + + cgs.widescreen.state = WIDESCREEN_RIGHT; + + y = 0; + if ( cg_drawFPS.integer ) { + y = CG_DrawFPS( y ); + } + if ( cg_drawTimer.integer ) { + y = CG_DrawTimer( y ); + } + + if ( cg_drawSnapshot.integer ) { + y = CG_DrawSnapshot( y ); + } + + if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 1 ) { + y = CG_DrawTeamOverlay( y, qtrue, qtrue ); + } + + cgs.widescreen.state = WIDESCREEN_NONE; + +/* if ( cg_drawAttacker.integer ) { //RPG-X - TiM: We don't really need this in an RP + y = CG_DrawAttacker( y ); + }*/ + +} + +/* +=========================================================================================== + + LOWER RIGHT CORNER + +=========================================================================================== +*/ + + + +/* +================= +CG_DrawScores + +Draw the small two score display +================= +*/ +static float CG_DrawScores( float y ) +{ +// const char *s; + int s1, s2; //, score; +// int x, w; +// int v; +// vec4_t color; + float y1; +// gitem_t *item; + + s1 = cgs.scores1; + s2 = cgs.scores2; + + y -= BIGCHAR_HEIGHT + 8; + y1 = y; + + return y1 - 8; +} + +/* +================ +CG_DrawPowerups +================ +*/ +static float CG_DrawPowerups( float y ) { + int sorted[MAX_POWERUPS]; + int sortedTime[MAX_POWERUPS]; + int i, j, k; + int active; + playerState_t *ps; + int t; + gitem_t *item; + int x; + int color; + float size; + float f; + static float colors[2][4] = { + { 0.2, 1.0, 0.2, 1.0 } , { 1.0, 0.2, 0.2, 1.0 } }; + int hasHoldable; + + hasHoldable = cg.snap->ps.stats[STAT_HOLDABLE_ITEM]; + + ps = &cg.snap->ps; + + if ( ps->stats[STAT_HEALTH] <= 0 ) { + return y; + } + + // sort the list by time remaining + active = 0; + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( !ps->powerups[ i ] ) { + continue; + } + t = ps->powerups[ i ] - cg.time; + // ZOID--don't draw if the power up has unlimited time (999 seconds) + // This is true of the CTF flags + if ( t < 0 || t > 999000) { + continue; + } + + // insert into the list + for ( j = 0 ; j < active ; j++ ) { + if ( sortedTime[j] >= t ) { + for ( k = active - 1 ; k >= j ; k-- ) { + sorted[k+1] = sorted[k]; + sortedTime[k+1] = sortedTime[k]; + } + break; + } + } + sorted[j] = i; + sortedTime[j] = t; + active++; + } + + // draw the icons and timers + x = 648; + for ( i = 0 ; i < active ; i++ ) { + + // Don't draw almost timed out powerups if we have more than 3 and a holdable item + if (!(hasHoldable && ipowerups[ sorted[i] ]; + if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { + trap_R_SetColor( NULL ); + } else { + vec4_t modulate; + + f = (float)( t - cg.time ) / POWERUP_BLINK_TIME; + f -= (int)f; + modulate[0] = modulate[1] = modulate[2] = modulate[3] = f; + trap_R_SetColor( modulate ); + } + + if ( cg.powerupActive == sorted[i] && + cg.time - cg.powerupTime < PULSE_TIME ) { + f = 1.0 - ( ( (float)cg.time - cg.powerupTime ) / PULSE_TIME ); + size = ICON_SIZE * ( 1.0 + ( PULSE_SCALE - 1.0 ) * f ); + } else { + size = ICON_SIZE; + } + + //CG_DrawPic( 640 - size, y + ICON_SIZE / 2 - size / 2, + // size, size, trap_R_RegisterShader( item->icon ) ); + x -= size + 10; + + CG_DrawPic( x, 478 - size, + size, size, trap_R_RegisterShader( item->icon ) ); + } + } + trap_R_SetColor( NULL ); + + return y; + +} + + +/* +===================== +CG_DrawLowerRight + +===================== +*/ +static void CG_DrawLowerRight( void ) { + float y; + + y = LOWEROVERLAY_Y; + + cgs.widescreen.state = WIDESCREEN_RIGHT; + + if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 2 ) { + y = CG_DrawTeamOverlay( y, qtrue, qfalse ); + } + + y = CG_DrawScores( y ); + y = CG_DrawPowerups( y ); + + cgs.widescreen.state = WIDESCREEN_NONE; +} + +/* +=================== +CG_DrawPickupItem +=================== +*/ +static int CG_DrawPickupItem( int y ) { + int value; + float *fadeColor; + + if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) { + return y; + } + + y -= ICON_SIZE; + + value = cg.itemPickup; + if ( value ) { + fadeColor = CG_FadeColor( cg.itemPickupTime, 3000 ); + if ( fadeColor ) { + CG_RegisterItemVisuals( value ); + trap_R_SetColor( fadeColor ); + CG_DrawPic( 8, y, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); + UI_DrawProportionalString( ICON_SIZE + 16, y + (ICON_SIZE/2 - BIGCHAR_HEIGHT/2), bg_itemlist[ value ].pickup_name, UI_SMALLFONT, fadeColor); + +// CG_DrawBigString( ICON_SIZE + 16, y + (ICON_SIZE/2 - BIGCHAR_HEIGHT/2), bg_itemlist[ value ].pickup_name, fadeColor[0] ); + trap_R_SetColor( NULL ); + } + } + + return y; +} + +/* +===================== +CG_DrawLowerLeft + +===================== +*/ +static void CG_DrawLowerLeft( void ) { + float y; + + y = LOWEROVERLAY_Y; + + cgs.widescreen.state = WIDESCREEN_LEFT; + + if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 3 ) { + y = CG_DrawTeamOverlay( y, qfalse, qfalse ); + } + + + y = CG_DrawPickupItem( y ); + + cgs.widescreen.state = WIDESCREEN_NONE; +} + + + +//=========================================================================================== + +/* +================= +CG_DrawTeamInfo +================= +*/ +static void CG_DrawTeamInfo( void ) { + int w, h; + int i, len; + vec4_t hcolor; + int chatHeight; + +#define CHATLOC_Y 420 // bottom end +#define CHATLOC_X 0 + + if (cg_teamChatHeight.integer < TEAMCHAT_HEIGHT) + chatHeight = cg_teamChatHeight.integer; + else + chatHeight = TEAMCHAT_HEIGHT; + if (chatHeight <= 0) + return; // disabled + + if (cgs.teamLastChatPos != cgs.teamChatPos) { + if (cg.time - cgs.teamChatMsgTimes[cgs.teamLastChatPos % chatHeight] > cg_teamChatTime.integer) { + cgs.teamLastChatPos++; + } + + h = (cgs.teamChatPos - cgs.teamLastChatPos) * TINYCHAR_HEIGHT; + + w = 0; + + for (i = cgs.teamLastChatPos; i < cgs.teamChatPos; i++) { + len = CG_DrawStrlen(cgs.teamChatMsgs[i % chatHeight]); + if (len > w) + w = len; + } + w *= TINYCHAR_WIDTH; + w += TINYCHAR_WIDTH * 2; + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { + hcolor[0] = 1; + hcolor[1] = 0; + hcolor[2] = 0; + hcolor[3] = 0.33; + } else if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 1; + hcolor[3] = 0.33; + } else { + hcolor[0] = 0; + hcolor[1] = 1; + hcolor[2] = 0; + hcolor[3] = 0.33; + } + + trap_R_SetColor( hcolor ); + CG_DrawPic( CHATLOC_X, CHATLOC_Y - h, 640, h, cgs.media.teamStatusBar ); + trap_R_SetColor( NULL ); + + hcolor[0] = hcolor[1] = hcolor[2] = 1.0; + hcolor[3] = 1.0; + + for (i = cgs.teamChatPos - 1; i >= cgs.teamLastChatPos; i--) { +// CG_DrawStringExt( CHATLOC_X + TINYCHAR_WIDTH, +// CHATLOC_Y - (cgs.teamChatPos - i)*TINYCHAR_HEIGHT, +// cgs.teamChatMsgs[i % chatHeight], hcolor, qfalse, qfalse, +// TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 ); + UI_DrawProportionalString( CHATLOC_X + TINYCHAR_WIDTH, + CHATLOC_Y - (cgs.teamChatPos - i)*TINYCHAR_HEIGHT, + cgs.teamChatMsgs[i % chatHeight], UI_TINYFONT, hcolor); + + } + } +} + +/* +=================== +CG_DrawHoldableItem +=================== +*/ +static void CG_DrawHoldableItem( void ) { + int value; + + value = cg.snap->ps.stats[STAT_HOLDABLE_ITEM]; + if ( value ) + { + CG_RegisterItemVisuals( value ); + if ( cg.snap->ps.stats[STAT_USEABLE_PLACED] && cg.snap->ps.stats[STAT_USEABLE_PLACED] != 2 ) + {//draw detpack... Borg 2-part teleporter will just draw the same until done + CG_DrawPic( 640-ICON_SIZE, 480-ICON_SIZE, ICON_SIZE, ICON_SIZE, cgs.media.detpackPlacedIcon ); + } + else + { + CG_DrawPic( 640-ICON_SIZE, 480-ICON_SIZE, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); + } + } + else + {//holding nothing... + if ( cg.snap->ps.stats[STAT_USEABLE_PLACED] > 0 ) + {//it's a timed countdown to getting a holdable, display the number in seconds + int sec; + char *s; + int w; + + sec = cg.snap->ps.stats[STAT_USEABLE_PLACED]; + + if ( sec < 0 ) + { + sec = 0; + } + + s = va( "%i", sec ); + + w = UI_ProportionalStringWidth(s,UI_BIGFONT); + UI_DrawProportionalString(640-(ICON_SIZE/2)-(w/2), (SCREEN_HEIGHT-ICON_SIZE)/2+(BIGCHAR_HEIGHT/2), s, UI_BIGFONT, colorTable[CT_WHITE]); + } + } +} + + +/* +=================== +CG_DrawReward +=================== +*/ +static void CG_DrawReward( void ) { + float *color; + int i; + float x, y; + + if ( !cg_drawRewards.integer ) { + return; + } + color = CG_FadeColor( cg.rewardTime, REWARD_TIME ); + if ( !color ) { + return; + } + + trap_R_SetColor( color ); + y = 56; + x = 320 - cg.rewardCount * ICON_SIZE/2; + for ( i = 0 ; i < cg.rewardCount ; i++ ) { + CG_DrawPic( x, y, ICON_SIZE-4, ICON_SIZE-4, cg.rewardShader ); + x += ICON_SIZE; + } + trap_R_SetColor( NULL ); +} + + +/* +=============================================================================== + +LAGOMETER + +=============================================================================== +*/ + +#define LAG_SAMPLES 128 + + +typedef struct { + int frameSamples[LAG_SAMPLES]; + int frameCount; + int snapshotFlags[LAG_SAMPLES]; + int snapshotSamples[LAG_SAMPLES]; + int snapshotCount; +} lagometer_t; + +lagometer_t lagometer; + +/* +============== +CG_AddLagometerFrameInfo + +Adds the current interpolate / extrapolate bar for this frame +============== +*/ +void CG_AddLagometerFrameInfo( void ) { + int offset; + + offset = cg.time - cg.latestSnapshotTime; + lagometer.frameSamples[ lagometer.frameCount & ( LAG_SAMPLES - 1) ] = offset; + lagometer.frameCount++; +} + +/* +============== +CG_AddLagometerSnapshotInfo + +Each time a snapshot is received, log its ping time and +the number of snapshots that were dropped before it. + +Pass NULL for a dropped packet. +============== +*/ +void CG_AddLagometerSnapshotInfo( snapshot_t *snap ) { + // dropped packet + if ( !snap ) { + lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = -1; + lagometer.snapshotCount++; + return; + } + + // add this snapshot's info + lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = snap->ping; + lagometer.snapshotFlags[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = snap->snapFlags; + lagometer.snapshotCount++; +} + +/* +============== +CG_DrawDisconnect + +Should we draw something differnet for long lag vs no packets? +============== +*/ +static void CG_DrawDisconnect( void ) { + float x, y; + int cmdNum; + usercmd_t cmd; + const char *s; + int w; + + // draw the phone jack if we are completely past our buffers + cmdNum = trap_GetCurrentCmdNumber() - CMD_BACKUP + 1; + trap_GetUserCmd( cmdNum, &cmd ); + if ( cmd.serverTime <= cg.snap->ps.commandTime || + cmd.serverTime > cg.time /*|| // special check for map_restart + cmd.serverTime < cg.snap->ps.introTime*/) // special check for holointro + { + return; + } + + // also add text in center of screen + s = ingame_text[IGT_CONNECTIONINTERRUPTED]; +// w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + w = UI_ProportionalStringWidth(s,UI_BIGFONT); +// CG_DrawBigString( 320 - w/2, 100, s, 1.0F); + // Used to be (Height) 100 + UI_DrawProportionalString(320 - w/2, 240, s, UI_BIGFONT, colorTable[CT_LTGOLD1]); + + // blink the icon + if ( ( cg.time >> 9 ) & 1 ) { + return; + } + + // RPG-X | Phenix | 08/06/2005 + x = 296; //640 - 50; + y = 182; + + CG_DrawPic( x, y, 48, 48, trap_R_RegisterShader("gfx/2d/net.tga" ) ); +} + + +#define MAX_LAGOMETER_PING 900 +#define MAX_LAGOMETER_RANGE 300 + +/* +============== +CG_DrawLagometer +============== +*/ +static void CG_DrawLagometer( void ) { + int a, x, y, i; + float v; + float ax, ay, aw, ah, mid, range; + int color; + float vscale; + + if ( !cg_lagometer.integer /* || cgs.localServer */) { + CG_DrawDisconnect(); + return; + } + + // + // draw the graph + // + // 640, 480 (-48) + x = 640 - 50; //move it left of the ammo numbers + y = 2; + + trap_R_SetColor( NULL ); + CG_DrawPic( x, y, 48, 48, cgs.media.lagometerShader ); + + ax = x; + ay = y; + aw = 48; + ah = 48; + CG_AdjustFrom640( &ax, &ay, &aw, &ah ); + + color = -1; + range = ah / 3; + mid = ay + range; + + vscale = range / MAX_LAGOMETER_RANGE; + + // draw the frame interpoalte / extrapolate graph + for ( a = 0 ; a < aw ; a++ ) { + i = ( lagometer.frameCount - 1 - a ) & (LAG_SAMPLES - 1); + v = lagometer.frameSamples[i]; + v *= vscale; + if ( v > 0 ) { + if ( color != 1 ) { + color = 1; + trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] ); + } + if ( v > range ) { + v = range; + } + trap_R_DrawStretchPic ( ax + aw - a, mid - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + } else if ( v < 0 ) { + if ( color != 2 ) { + color = 2; + trap_R_SetColor( g_color_table[ColorIndex(COLOR_BLUE)] ); + } + v = -v; + if ( v > range ) { + v = range; + } + trap_R_DrawStretchPic( ax + aw - a, mid, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + } + } + + // draw the snapshot latency / drop graph + range = ah / 2; + vscale = range / MAX_LAGOMETER_PING; + + for ( a = 0 ; a < aw ; a++ ) { + i = ( lagometer.snapshotCount - 1 - a ) & (LAG_SAMPLES - 1); + v = lagometer.snapshotSamples[i]; + if ( v > 0 ) { + if ( lagometer.snapshotFlags[i] & SNAPFLAG_RATE_DELAYED ) { + if ( color != 5 ) { + color = 5; // YELLOW for rate delay + trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] ); + } + } else { + if ( color != 3 ) { + color = 3; + trap_R_SetColor( g_color_table[ColorIndex(COLOR_GREEN)] ); + } + } + v = v * vscale; + if ( v > range ) { + v = range; + } + trap_R_DrawStretchPic( ax + aw - a, ay + ah - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + } else if ( v < 0 ) { + if ( color != 4 ) { + color = 4; // RED for dropped snapshots + trap_R_SetColor( g_color_table[ColorIndex(COLOR_RED)] ); + } + trap_R_DrawStretchPic( ax + aw - a, ay + ah - range, 1, range, 0, 0, 0, 0, cgs.media.whiteShader ); + } + } + + trap_R_SetColor( NULL ); + + if ( cg_nopredict.integer || cg_synchronousClients.integer ) { +// CG_DrawBigString( ax, ay, "snc", 1.0 ); + UI_DrawProportionalString(ax, ay, "snc", UI_BIGFONT, colorTable[CT_LTGOLD1]); + } + + 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, int y, int charWidth ) { + char *s; + + Q_strncpyz( cg.centerPrint, str, sizeof(cg.centerPrint) ); + + cg.centerPrintTime = cg.time; + cg.centerPrintY = y; + cg.centerPrintCharWidth = charWidth; + + // count the number of lines for centering + cg.centerPrintLines = 1; + s = cg.centerPrint; + while( *s ) { + if (*s == '\n') + cg.centerPrintLines++; + s++; + } +} + + +/* +=================== +CG_DrawCenterString +=================== +*/ +static void CG_DrawCenterString( void ) { + char *start; + int l; + int x, y, w; + float *color; + + if ( !cg.centerPrintTime ) { + return; + } + + color = CG_FadeColor( cg.centerPrintTime, 1000 * cg_centertime.value ); + if ( !color ) { + return; + } + + trap_R_SetColor( color ); + + start = cg.centerPrint; + + y = cg.centerPrintY - cg.centerPrintLines * BIGCHAR_HEIGHT / 2; + + while ( 1 ) { + char linebuffer[1024]; + + for ( l = 0; l < 60; l++ ) { + if ( !start[l] || start[l] == '\n' ) { + break; + } + linebuffer[l] = start[l]; + } + linebuffer[l] = 0; + +// w = cg.centerPrintCharWidth * CG_DrawStrlen( linebuffer ); + w = UI_ProportionalStringWidth(linebuffer,UI_BIGFONT); + + x = ( SCREEN_WIDTH - w ) / 2; + +// CG_DrawStringExt( x, y, linebuffer, color, qfalse, qtrue, +// cg.centerPrintCharWidth, (int)(cg.centerPrintCharWidth * 1.5), 0 ); + + UI_DrawProportionalString( x, y, linebuffer, UI_BIGFONT|UI_DROPSHADOW, color); + + y += cg.centerPrintCharWidth * 1.5; + + while ( *start && ( *start != '\n' ) ) { + start++; + } + if ( !*start ) { + break; + } + start++; + } + + trap_R_SetColor( NULL ); +} + + + +/* +================================================================================ + +CROSSHAIR + +================================================================================ +*/ + +/*qboolean CG_WorldCoordToScreenCoordFloat(vec3_t worldCoord, float *x, float *y) +{ + float 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.0f / 2.0f;//gives screen coords in virtual 640x480, to be adjusted when drawn + ycenter = 480.0f / 2.0f;//gives screen coords in virtual 640x480, to be adjusted when drawn + + AngleVectors (cg.refdefViewAngles, 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.01f) + { + return qfalse; + } + + xzi = xcenter / transformed[2] * (96.0f/cg.refdef.fov_x); + yzi = ycenter / transformed[2] * (102.0f/cg.refdef.fov_y); + + *x = xcenter + xzi * transformed[0]; + *y = ycenter - yzi * transformed[1]; + + return qtrue; +}*/ + +/*float cg_crosshairPrevPosX = 0; +float cg_crosshairPrevPosY = 0; +#define CRAZY_CROSSHAIR_MAX_ERROR_X (100.0f*640.0f/480.0f) +#define CRAZY_CROSSHAIR_MAX_ERROR_Y (100.0f) +void CG_LerpCrosshairPos( float *x, float *y ) +{ + if ( cg_crosshairPrevPosX ) + {//blend from old pos + float maxMove = 100.0f * ((float)cg.frametime/500.0f) * 640.0f/480.0f; //30 + float xDiff = (*x - cg_crosshairPrevPosX); + if ( fabs(xDiff) > CRAZY_CROSSHAIR_MAX_ERROR_X ) + { + maxMove = CRAZY_CROSSHAIR_MAX_ERROR_X; + } + if ( xDiff > maxMove ) + { + *x = cg_crosshairPrevPosX + maxMove; + } + else if ( xDiff < -maxMove ) + { + *x = cg_crosshairPrevPosX - maxMove; + } + } + cg_crosshairPrevPosX = *x; + + if ( cg_crosshairPrevPosY ) + {//blend from old pos + float maxMove = 100.0f * ((float)cg.frametime/500.0f); + float yDiff = (*y - cg_crosshairPrevPosY); + if ( fabs(yDiff) > CRAZY_CROSSHAIR_MAX_ERROR_Y ) + { + maxMove = CRAZY_CROSSHAIR_MAX_ERROR_X; + } + if ( yDiff > maxMove ) + { + *y = cg_crosshairPrevPosY + maxMove; + } + else if ( yDiff < -maxMove ) + { + *y = cg_crosshairPrevPosY - maxMove; + } + } + cg_crosshairPrevPosY = *y; +}*/ + +/* +================= +CG_CalcMuzzlePoint +**Blatently plagiarised from JKA** + +Um, I guess this calculates the approximate vector +of where your gun is at ingame. :P +=================*/ + + +//static qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle ) { +// vec3_t forward, right; +// vec3_t gunpoint; +// centity_t *cent; +// int anim; +// +// if ( entityNum == cg.snap->ps.clientNum ) +// { //I'm not exactly sure why we'd be rendering someone else's crosshair, but hey. +// int weapontype = cg.snap->ps.weapon; +// vec3_t weaponMuzzle = {13, 6, -6}; +// centity_t *pEnt = &cg_entities[cg.predictedPlayerState.clientNum]; +// +// if (cg.renderingThirdPerson) +// { +// VectorCopy( pEnt->lerpOrigin, gunpoint ); //lerp +// AngleVectors( pEnt->lerpAngles, forward, right, NULL ); +// } +// /*else +// { +// VectorCopy( cg.refdef.vieworg, gunpoint ); +// AngleVectors( cg.refdefViewAngles, forward, right, NULL ); +// }*/ +// +// VectorCopy(gunpoint, muzzle); +// +// VectorMA(muzzle, weaponMuzzle[0], forward, muzzle); +// VectorMA(muzzle, weaponMuzzle[1], right, muzzle); +// +// if (cg.renderingThirdPerson) +// { +// muzzle[2] += cg.snap->ps.viewheight + weaponMuzzle[2]; +// } +// /*else +// { +// muzzle[2] += weaponMuzzle[2]; +// }*/ +// +// return qtrue; +// } +// +// cent = &cg_entities[entityNum]; +// if ( !cent->currentValid ) { +// return qfalse; +// } +// +// VectorCopy( cent->currentState.pos.trBase, muzzle ); +// +// AngleVectors( cent->currentState.apos.trBase, forward, NULL, NULL ); +// anim = cent->currentState.legsAnim; +// if ( anim == BOTH_CROUCH1IDLE || anim == BOTH_CROUCH1WALK ) { +// muzzle[2] += CROUCH_VIEWHEIGHT; +// } else { +// muzzle[2] += DEFAULT_VIEWHEIGHT; +// } +// +// VectorMA( muzzle, 14, forward, muzzle ); +// +// return qtrue; +// +// +//} + +//end dCross + +/* +================= +CG_DrawCrosshair +================= +*/ +static void CG_DrawCrosshair(void) { + float w, h; + //qhandle_t hShader; + float f; + float x = 0; + float y = 0; //float + int weaponCrosshairNum; + + //dCross + trace_t trace; + vec3_t start, end; + int ignore; + vec3_t d_f; + vec3_t pitchConstraint; + vec3_t worldPoint; + + crosshairsData_t *cd; + + if( cg.zoomed ) { //RPG-X - TiM: We dun need crosshairs when zoomed anymore :P + return; + } + + if ( !cg_drawCrosshair.integer ) { + return; + } + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR /*|| (cg.snap->ps.eFlags&EF_ELIMINATED)*/ ) { + return; + } + + //clamp crosshair num + if ( (weaponCrosshairNum = cg.predictedPlayerState.weapon - 1) < 0 ){ + weaponCrosshairNum = 0; + } + else if ( weaponCrosshairNum >= MAX_CROSSHAIRS ) { + weaponCrosshairNum = 14; + } + + cd = &cgs.crosshairsData[weaponCrosshairNum]; + + ignore = cg.predictedPlayerState.clientNum; + + //if noDraw was specified in the crosshair script + if ( cd->noDraw ) { + return; + } + + //TiM: With the new crosshair rendering system, this should be no problem + /*if ( cg.snap->ps.weapon == WP_NULL_HAND ) { //Teh hand has no crosshair + return; + }*/ + +// if ( cg.renderingThirdPerson ) { +// return; +// } + +//We don't need this anymore (RPG-X: J2J) +/* + // set color based on health + if ( cg_crosshairHealth.integer ) { + vec4_t hcolor; + + CG_ColorForHealth( hcolor ); + trap_R_SetColor( hcolor ); + } else { + trap_R_SetColor( NULL ); +// }*/ + + w = h = cg_crosshairSize.value; + + // pulse the size of the crosshair when picking up items + f = cg.time - cg.itemPickupBlendTime; + if ( f > 0 && f < ITEM_BLOB_TIME ) { + f /= ITEM_BLOB_TIME; + w *= ( 1 + f ); + h *= ( 1 + f ); + } + + //dCross + + if( cg_dynamicCrosshair.value == 1 && cg.renderingThirdPerson) { + + //if ( cg.renderingThirdPerson ) { + VectorCopy( cg.predictedPlayerState.viewangles, pitchConstraint); //cg.predictedPlayerState.viewangles //cg.refdefViewAngles //vieworg + //} + /*else + { + VectorCopy(cg.refdefViewAngles, pitchConstraint); + }*/ + + AngleVectors( pitchConstraint, d_f, NULL, NULL ); + + //CG_CalcMuzzlePoint(cg.snap->ps.clientNum, start); + //if ( cg.renderingThirdPerson ) { + VectorCopy( cg.predictedPlayerState.origin, start); + if ( !(cg.predictedPlayerState.eFlags & EF_FULL_ROTATE) && Q_fabs( cg.predictedPlayerState.viewangles[PITCH] ) > 89.9f ) + start[2] -= 20; + else + start[2] += (float)cg.predictedPlayerState.viewheight * cgs.clientinfo[cg.predictedPlayerState.clientNum].height; + //} + // else { + // VectorCopy( cg.refdef.vieworg, start); + // } + + VectorMA( start, 6000.0f, d_f, end ); //cg.distanceCull + + CG_Trace( &trace, start, vec3_origin, vec3_origin, end, ignore, CONTENTS_SOLID|CONTENTS_BODY ); + + //TiM - if we hit a cloaked admin, bypass them so the crosshair doesn't jump randomly + //NOTE: Possibly could cause errors + while ( cg_entities[trace.entityNum].currentState.powerups & ( 1 <ps.weapon) + { + default: + case WP_PHASER: hShader = cgs.media.crosshair[0]; break; + case WP_COMPRESSION_RIFLE: hShader = cgs.media.crosshair[1]; break; + case WP_NULL_HAND: hShader = cgs.media.crosshair[4]; break; + case WP_COFFEE: hShader = cgs.media.crosshair[2]; break; + case WP_DISRUPTOR: hShader = cgs.media.crosshair[3]; break; + case WP_GRENADE_LAUNCHER: hShader = cgs.media.crosshair[6]; break; + case WP_TR116: hShader = cgs.media.crosshair[5]; break; + case WP_QUANTUM_BURST: hShader = cgs.media.crosshair[7]; break; + case WP_DERMAL_REGEN: hShader = cgs.media.crosshair[8]; break; + case WP_VOYAGER_HYPO: hShader = cgs.media.crosshair[9]; break; + case WP_TOOLKIT: hShader = cgs.media.crosshair[11]; break; + case WP_MEDKIT: hShader = cgs.media.crosshair[10]; break; + case WP_TRICORDER: hShader = cgs.media.crosshair[14]; break; + case WP_PADD: hShader = cgs.media.crosshair[13]; break; + case WP_NEUTRINO_PROBE: hShader = cgs.media.crosshair[12]; break; + }*/ + + //If admins scan non-players + if ( cg.predictedPlayerState.weapon == WP_TRICORDER && cg.predictedPlayerState.eFlags & EF_FIRING ) { + if (/*cg.predictedPlayerState.persistant[PERS_CLASS] == PC_ADMIN*/cg_showEntityNums.integer && cgs.clientinfo[cg.snap->ps.clientNum].isAdmin && cg.crosshairClientNum < ENTITYNUM_WORLD ) { + vec4_t ccolor; + /*color[0] = colorTable[CT_YELLOW][0]; + color[1] = colorTable[CT_YELLOW][1]; + color[2] = colorTable[CT_YELLOW][2];*/ + /*color[0] = 0.9F;//R + color[1] = 0.7F;//G + color[2] = 0.0F;//B + color[3] = 0.8;*/ + ccolor[0] = 0.694f;//0.9F;//R + ccolor[1] = 0.816f;//0.7F;//G + ccolor[2] = 1.0f;//0.0F;//B + ccolor[3] = 0.8f; + + //TiM + cgs.widescreen.state = WIDESCREEN_CENTER; + + UI_DrawProportionalString(x + 320, + y + 270, + va("Entity: %i", cg.crosshairClientNum), + UI_CENTER|UI_SMALLFONT, + ccolor); //170 + + //CG_Printf( "x= %i, y = %i, w = %i, h = %i\n", cg.refdef.x, cg.refdef.y, cg.refdef.width, cg.refdef.height ); + } + /*if(cg_entities[cg.crosshairClientNum].currentState.modelindex == HI_SHIELD && cg_entities[cg.crosshairClientNum].currentState.apos.trBase[0] != 0) { + vec4_t ccolor; + ccolor[0] = 0.694f; + ccolor[1] = 0.816f; + ccolor[2] = 1.0f; + ccolor[3] = 0.8f; + UI_DrawProportionalString(x + 320, + y + 285, + va("Frequency: %f", cg_entities[cg.crosshairClientNum].currentState.apos.trBase[0]), + UI_CENTER|UI_SMALLFONT, + ccolor); + }*/ + } + + cgs.widescreen.state = WIDESCREEN_LEFT; + CG_AdjustFrom640( &x, &y, &w, &h ); + + trap_R_SetColor( cd->color ); + + //TiM: Huh... we have a problem cap'n. + //Even though we have absolutely perfect alignment, the ingame drawing (regardless of mipmapping) + //appears to be blurring the icons to the point where they overlap, leaving little smudges at the corners + //of certain crosshairs ingame :'( + //So I'm attempting to fix this by creating a very very subtle offset to scale the scan region inwards a bit. + //Addendum: FRAK! Okay... offsetting will not work. It clips any of the hairs that are in their full boundary. Which looks crap :P + //Com_Printf("s1 = %f, t1 = %f, s2 = %f, t2 = %f\n", ((float)cd->s1/128.0f), ((float)cd->t1/128.0f), ((float)cd->s2/128.0f), ((float)cd->t2/128.0f)); + + //Magic number! 0.0078125 = 1 pixel in a 128x128 bitmap - Edited out. 1 pixel = WAY TOO MUCH! + trap_R_DrawStretchPic( x + cg.refdef.x + 0.5 * (cg.refdef.width - w), //X + y + cg.refdef.y + 0.5 * (cg.refdef.height - h), //Y + w, h, //W+H + ((float)cd->s1/128.0f), ((float)cd->t1/128.0f), //s1 + t1 + ((float)cd->s2/128.0f), ((float)cd->t2/128.0f), //s2 + t2 + cgs.media.crosshairSheet ); + + trap_R_SetColor( NULL ); +} + +/* +================= +CG_LabelCrosshairEntity +================= +*/ + +static void CG_LabelViewEntity( int clientNum, vec3_t origin, vec3_t entMins, vec3_t entMaxs, char *name, qboolean scanAll, vec4_t color, qboolean drawHealth, int health, char *pClass, char *rank, char *race, char* age, char *height, char *weight, char *weapon ) +{//ID teammates, ID enemies, ID objectives, etc. + centity_t *cent; + //clientInfo_t *ci; + vec3_t center, maxs, mins, top, bottom, topLeft, topRight, bottomLeft, bottomRight; + vec3_t worldEast = {1.0f, 0, 0}, worldNorth = {0, 1.0f, 0}, worldUp = {0, 0, 1.0f}; + //vec4_t hcolor; + float x = 0, y = 0; + float topLeftx, topLefty, topRightx, topRighty, bottomLeftx, bottomLefty, bottomRightx, bottomRighty; + int corner, topSize, bottomSize, leftSize, rightSize; + int charIndex, classCharIndex, rankCharIndex, ageCharIndex, raceCharIndex, htCharIndex, wtCharIndex, weapCharIndex, healthCharIndex; + float lineHorzLength = 8.0f, lineVertLength = 8.0f, lineWidth = 2.0f; + float fUpDot, fEastDot, fNorthDot, uNorthDot, uEastDot;//, hwidth;//, timedScale = 1.0f; + qboolean doTopLeft = qfalse; + qboolean doTopRight = qfalse; + qboolean doBottomLeft = qfalse; + qboolean doBottomRight = qfalse; + qboolean doSizes = qtrue; + float w; + char showName[1024]; + char showRank[1024]; + char showRace[1024]; + char showHt[1024]; + char showWt[1024]; + char showWeap[1024]; + char showHealth[1024]; + char showAge[1024]; + char showClass[1024]; + //char *health = "100"; + + cent = &cg_entities[clientNum]; + + /*if ( clientNum < MAX_CLIENTS ) { + ci = &cgs.clientinfo[clientNum]; + }*/ + + infoStringCount += cg.frametime; + rankCharIndex = raceCharIndex = classCharIndex = ageCharIndex = htCharIndex = wtCharIndex = weapCharIndex = charIndex = healthCharIndex = floor(infoStringCount/33); + //TODO: have box scale in from corners of screen? Or out from center? + /* + if(infoStringCount < 1000) + { + timedScale = (float)infoStringCount/100.0f; + timedScale = 10.0f - timedScale; + if(timedScale < 1.0f) + { + timedScale = 1.0f; + } + } + */ + //IDEA: We COULD actually rotate a wire-mesh version of the crossEnt until it + // matches the crossEnt's angles then flash it and pop up this info... + // but that would be way too much work for something like this. + // Alternately, could rotate a scaled-down fully-skinned version + // next to it, but that, too, might be overkill... (plus, model would + // need back faces) + + //FIXME: can be optimized... + + //Draw frame around ent's bbox + //FIXME: make global, do once + fUpDot = 1.0f - fabs( DotProduct( vfwd_n, worldUp ) );//1.0 if looking up or down, so use mins and maxs more + fEastDot = fabs( DotProduct( vfwd_n, worldEast ) );//1.0 if looking east or west, so use mins[1] and maxs[1] more + fNorthDot = fabs( DotProduct( vfwd_n, worldNorth ) );//1.0 if looking north or south, so use mins[0] and maxs[0] more + uEastDot = fabs( DotProduct( vup_n, worldEast ) );//1.0 if looking up or down, head towards east or west, so use mins[0] and maxs[0] more + uNorthDot = fabs( DotProduct( vup_n, worldNorth ) );//1.0 if looking up or down, head towards north or south, so use mins[1] and maxs[1] more + + /*if ( crossEnt->s.solid == SOLID_BMODEL ) + {//brush model, no origin, so use the center + VectorAdd( crossEnt->absmin, crossEnt->absmax, center ); + VectorScale( center, 0.5, center ); + VectorSubtract( crossEnt->absmax, center, maxs ); + VectorSubtract( crossEnt->absmin, center, mins ); + } + else + {*/ + VectorCopy( origin, center ); //crossEnt->currentOrigin//cent->lerpOrigin + VectorCopy( entMaxs, maxs ); //crossEnt->maxs //playerMaxs + VectorCopy( entMins, mins ); //crossEnt->mins //playerMins + //} + + //NOTE: this presumes that mins[0] and maxs[0] are symmetrical and mins[1] and maxs[1] as well + topSize = (maxs[2]*fUpDot + maxs[1]*uNorthDot + maxs[0]*uEastDot);//* timedScale + bottomSize = (mins[2]*fUpDot + mins[1]*uNorthDot + mins[0]*uEastDot);//* timedScale + leftSize = (fUpDot*(mins[0]*fNorthDot + mins[1]*fEastDot) + mins[0]*uNorthDot + mins[1]*uEastDot);//* timedScale + rightSize = (fUpDot*(maxs[0]*fNorthDot + maxs[1]*fEastDot) + maxs[0]*uNorthDot + maxs[1]*uEastDot);//* timedScale + + //Find corners + //top + VectorMA( center, topSize, vup_n, top ); + //bottom + VectorMA( center, bottomSize, vup_n, bottom ); + //Top-left frame + VectorMA( top, leftSize, vright_n, topLeft ); + //Top-right frame + VectorMA( top, rightSize, vright_n, topRight ); + //bottom-left frame + VectorMA( bottom, leftSize, vright_n, bottomLeft ); + //bottom-right frame + VectorMA( bottom, rightSize, vright_n, bottomRight ); + + if ( CG_WorldCoordToScreenCoord( topLeft, &topLeftx, &topLefty, qfalse ) ) + { + doTopLeft = qtrue; + } + else + { + doSizes = qfalse; + } + + if ( CG_WorldCoordToScreenCoord( topRight, &topRightx, &topRighty, qfalse ) ) + { + doTopRight = qtrue; + } + else + { + doSizes = qfalse; + } + + if ( CG_WorldCoordToScreenCoord( bottomLeft, &bottomLeftx, &bottomLefty, qfalse ) ) + { + doBottomLeft = qtrue; + } + else + { + doSizes = qfalse; + } + + if ( CG_WorldCoordToScreenCoord( bottomRight, &bottomRightx, &bottomRighty, qfalse ) ) + { + doBottomRight = qtrue; + } + else + { + doSizes = qfalse; + } + + //NOTE: maybe print color-coded "Primary/Secondary Objective" on top if an objective? + for ( corner = 0; corner < 13; corner++ ) //11 + {//FIXME: make sure line length of 8 isn't greater than width of object + switch ( corner ) + { + case 0://top-left + if ( doTopLeft ) + { + if ( doSizes ) + { + //Line lengths + lineVertLength = (bottomLefty-topLefty)*0.25f; + lineHorzLength = (topRightx-topLeftx)*0.25f; + } + CG_FillRect( topLeftx + 2, topLefty, lineHorzLength, lineWidth, color ); + CG_FillRect( topLeftx, topLefty, lineWidth, lineVertLength, color ); + } + break; + case 1://top-right + if ( doTopRight ) + { + if ( doSizes ) + { + //Line lengths + lineVertLength = (bottomRighty-topRighty)*0.25f; + lineHorzLength = (topRightx-topLeftx)*0.25f; + } + CG_FillRect( topRightx-lineHorzLength, topRighty, lineHorzLength, lineWidth, color ); + CG_FillRect( topRightx, topRighty, lineWidth, lineVertLength, color ); + } + break; + case 2://bottom-left + if ( doBottomLeft ) + { + if ( doSizes ) + { + //Line lengths + lineVertLength = (bottomLefty-topLefty)*0.25f; + lineHorzLength = (bottomRightx-bottomLeftx)*0.25f; + } + CG_FillRect( bottomLeftx, bottomLefty, lineHorzLength, lineWidth, color ); + CG_FillRect( bottomLeftx, bottomLefty-lineVertLength, lineWidth, lineVertLength, color ); + } + break; + case 3://bottom-right + if ( doBottomRight ) + { + if ( doSizes ) + { + //Line lengths + lineVertLength = (bottomRighty-topRighty)*0.25f; + lineHorzLength = (bottomRightx-bottomLeftx)*0.25f; + } + CG_FillRect( bottomRightx-lineHorzLength, bottomRighty, lineHorzLength, lineWidth, color ); + CG_FillRect( bottomRightx, bottomRighty-lineVertLength, lineWidth, lineVertLength + 2, color ); + } + break; + case 4://healthBar + if ( charIndex > 0 ) + { + /* + //tried to keep original functionality, but it would pop from top to bottom + //when you let go of the button and had no way to tell then (during the + //fade-out) whether it should be on top or bottom. So now it is always on top. + if ( !scanAll ) + { + if ( !CG_WorldCoordToScreenCoord( bottom, &x, &y, qfalse ) ) + {//Can't draw bottom + return; + } + } + else + */ + {//try to draw at top as to not obscure the tricorder + CG_WorldCoordToScreenCoord( top, &x, &y, qtrue ); + if ( y > 0.01 ) + { + y -= SMALLCHAR_HEIGHT; + if ( y > 0.01 ) + { + if ( charIndex > 0 && name ) + { + if ( y >= SMALLCHAR_HEIGHT ) + { + y -= SMALLCHAR_HEIGHT; + } + else + { + y = 0.01; + } + } + if ( y > 0.01 ) + { + if ( rankCharIndex > 0 && rank ) + { + if ( y >= SMALLCHAR_HEIGHT ) + { + y -= SMALLCHAR_HEIGHT; + } + } + if ( y > 0.01 ) + { + if ( ageCharIndex > 0 && age ) + { + if ( y >= SMALLCHAR_HEIGHT ) + { + y -= SMALLCHAR_HEIGHT; + } + } + if ( y > 0.01 ) + { + if ( classCharIndex > 0 && pClass ) + { + if ( y >= SMALLCHAR_HEIGHT ) + { + y -= SMALLCHAR_HEIGHT; + } + } + if ( y > 0.01 ) + { + if ( raceCharIndex > 0 && race ) + { + if ( y >= SMALLCHAR_HEIGHT ) + { + y -= SMALLCHAR_HEIGHT; + } + } + if ( y > 0.01 ) + { + if ( htCharIndex > 0 && height ) + { + if ( y >= SMALLCHAR_HEIGHT ) + { + y -= SMALLCHAR_HEIGHT; + } + } + if ( y > 0.01 ) + { + if ( wtCharIndex > 0 && weight ) + { + if ( y >= SMALLCHAR_HEIGHT ) + { + y -= SMALLCHAR_HEIGHT; + } + } + if ( y > 0.01 ) + { + if ( weapCharIndex > 0 && weapon ) + { + if ( y >= SMALLCHAR_HEIGHT ) + { + y -= SMALLCHAR_HEIGHT; + } + } + } + } + } + } + } + } + } + } + } + } + + if ( !color[0] && !color[1] && !color[2] ) + { + // We really don't want black, so set it to yellow + color[0] = 0.9F;//R + color[1] = 0.7F;//G + color[2] = 0.0F;//B + } + color[3] = 0.75; + + if ( !drawHealth || !health ) + { + continue; + } + + //health = ci->health; //health, max_health //ceil( (float)ci->health/(float)100.0f*100.0f ); + //CG_ColorForGivenHealth( hcolor, health ); + //hwidth = (float)health*0.5f; + + //y += lineWidth + 2; + + //CG_FillRect( x - hwidth/2, y + lineWidth, hwidth, lineWidth*2, hcolor ); + + //y += lineWidth*2; + + Com_sprintf( showHealth, sizeof( showHealth ), "%s: %i", "Health", health ); + + if ( healthCharIndex > 0 && showHealth ) { + int len = strlen( showHealth ); + + if ( healthCharIndex > len+1 ) + { + healthCharIndex = len+1; + } + else + { + trap_S_StartSound( NULL, 0, CHAN_ITEM, cgs.media.tedTextSound ); + } + //Q_strncpyz( showHealth, showHealth, healthCharIndex ); + w = CG_DrawStrlen( showHealth ) * SMALLCHAR_WIDTH; + Q_strncpyz( showHealth, showHealth, healthCharIndex ); + CG_DrawSmallStringColor( x - w / 2, y + lineWidth, showHealth, color ); + y += SMALLCHAR_HEIGHT; + } + } + break; + case 5://infoString (name/description) + //Bright yellow + //VectorCopy( crossEnt->startRGBA, color ); + + /*if ( !color[0] && !color[1] && !color[2] ) + { + // We really don't want black, so set it to yellow + color[0] = 0.9F;//R + color[1] = 0.7F;//G + color[2] = 0.0F;//B + } + color[3] = 0.75;*/ + if ( charIndex > 0 && name ) + { + int len = strlen(name); + if ( charIndex > len+1 ) + { + charIndex = len+1; + } + else + { + trap_S_StartSound( NULL, 0, CHAN_ITEM, cgs.media.tedTextSound ); + } + Q_strncpyz( showName, name, charIndex ); + w = CG_DrawStrlen( name ) * SMALLCHAR_WIDTH; + CG_DrawSmallStringColor( x - w / 2, y + lineWidth, showName, color ); + y += SMALLCHAR_HEIGHT; + } + break; + case 6://class + if ( classCharIndex > 0 && pClass ) + { + int len = strlen(pClass); + if ( classCharIndex > len+1 ) + { + classCharIndex = len+1; + } + else + { + trap_S_StartSound( NULL, 0, CHAN_ITEM, cgs.media.tedTextSound ); + } + Q_strncpyz( showClass, pClass, classCharIndex ); + w = CG_DrawStrlen( pClass ) * SMALLCHAR_WIDTH; + CG_DrawSmallStringColor( x - w / 2, y + lineWidth, showClass, color ); + y += SMALLCHAR_HEIGHT; + } + break; + case 7://rank + if ( rankCharIndex > 0 && rank ) + { + int len = strlen(rank); + if ( rankCharIndex > len+1 ) + { + rankCharIndex = len+1; + } + else + { + trap_S_StartSound( NULL, 0, CHAN_ITEM, cgs.media.tedTextSound ); + } + Q_strncpyz( showRank, rank, rankCharIndex ); + w = CG_DrawStrlen( rank ) * SMALLCHAR_WIDTH; + CG_DrawSmallStringColor( x - w / 2, y + lineWidth, showRank, color ); + y += SMALLCHAR_HEIGHT; + } + break; + case 8://age + if ( ageCharIndex > 0 && age ) + { + int len = strlen(age); + if ( ageCharIndex > len+1 ) + { + ageCharIndex = len+1; + } + else + { + trap_S_StartSound( NULL, 0, CHAN_ITEM, cgs.media.tedTextSound ); + } + Q_strncpyz( showAge, age, ageCharIndex ); + w = CG_DrawStrlen( age ) * SMALLCHAR_WIDTH; + CG_DrawSmallStringColor( x - w / 2, y + lineWidth, showAge, color ); + y += SMALLCHAR_HEIGHT; + } + break; + case 9://race + if ( raceCharIndex > 0 && race ) + { + int len = strlen(race); + if ( raceCharIndex > len+1 ) + { + raceCharIndex = len+1; + } + else + { + trap_S_StartSound( NULL, 0, CHAN_ITEM, cgs.media.tedTextSound ); + } + Q_strncpyz( showRace, race, raceCharIndex ); + w = CG_DrawStrlen( race ) * SMALLCHAR_WIDTH; + CG_DrawSmallStringColor( x - w / 2, y + lineWidth, showRace, color ); + y += SMALLCHAR_HEIGHT; + } + break; + case 10://height + if ( htCharIndex > 0 && height ) + { + int len = strlen(height); + if ( htCharIndex > len+1 ) + { + htCharIndex = len+1; + } + else + { + trap_S_StartSound( NULL, 0, CHAN_ITEM, cgs.media.tedTextSound ); + } + Q_strncpyz( showHt, height, htCharIndex ); + w = CG_DrawStrlen( height ) * SMALLCHAR_WIDTH; + CG_DrawSmallStringColor( x - w / 2, y + lineWidth, showHt, color ); + y += SMALLCHAR_HEIGHT; + } + break; + case 11://weight + if ( wtCharIndex > 0 && weight ) + { + int len = strlen(weight); + if ( wtCharIndex > len+1 ) + { + wtCharIndex = len+1; + } + else + { + trap_S_StartSound( NULL, 0, CHAN_ITEM, cgs.media.tedTextSound ); + } + Q_strncpyz( showWt, weight, wtCharIndex ); + w = CG_DrawStrlen( weight ) * SMALLCHAR_WIDTH; + CG_DrawSmallStringColor( x - w / 2, y + lineWidth, showWt, color ); + y += SMALLCHAR_HEIGHT; + } + break; + case 12://weapon + if ( weapCharIndex > 0 && weapon ) + { + int len = strlen(weapon); + if ( weapCharIndex > len+1 ) + { + weapCharIndex = len+1; + } + else + { + trap_S_StartSound( NULL, 0, CHAN_ITEM, cgs.media.tedTextSound ); + } + Q_strncpyz( showWeap, weapon, weapCharIndex ); + w = CG_DrawStrlen( weapon ) * SMALLCHAR_WIDTH; + CG_DrawSmallStringColor( x - w / 2, y + lineWidth, showWeap, color ); + y += SMALLCHAR_HEIGHT; + } + break; + } + } +} + +/* +================= +CG_ScanForCrosshairEntity +================= +*/ +static void CG_ScanForCrosshairEntity( void ) { + trace_t trace; + vec3_t start, end; + int content; + vec3_t pitchConstraint, df_f; + + VectorCopy( cg.predictedPlayerState.origin, start ); //cg.refdef.vieworg + start[2] += (float)cg.predictedPlayerState.viewheight * cgs.clientinfo[cg.predictedPlayerState.clientNum].height; + //VectorCopy( cg.predictedPlayerState.origin, start); + //start[2] += cg.predictedPlayerState.viewheight; + + VectorCopy( cg.predictedPlayerState.viewangles, pitchConstraint ); + AngleVectors( pitchConstraint, df_f, NULL, NULL ); + + VectorMA( start, 8912, df_f, end); + + //VectorMA( start, 8192, cg.refdef.viewaxis[0], end ); + + if ( cg.snap->ps.weapon == WP_TR116 && cg.zoomed ) { + CG_Trace( &trace, start, vec3_origin, vec3_origin, end, + cg.snap->ps.clientNum, CONTENTS_BODY ); + + // if the player is invisible, don't show it + if ( cg_entities[ trace.entityNum ].currentState.powerups & ( 1 << PW_INVIS ) && !cgs.clientinfo[cg.snap->ps.clientNum].isAdmin/*cg.snap->ps.persistant[PERS_CLASS] != PC_ADMIN*/ ) { + return; + } + } + else { + CG_Trace( &trace, start, vec3_origin, vec3_origin, end, + cg.snap->ps.clientNum, MASK_SHOT ); //CONTENTS_SOLID|CONTENTS_BODY + + if ( cg.predictedPlayerState.weapon == WP_TRICORDER && cg.predictedPlayerState.eFlags & EF_FIRING + && (cg_entities[trace.entityNum].currentState.eType == ET_TRIC_STRING || cg_entities[trace.entityNum].currentState.eType == ET_MOVER_STR) ) + { + //Never mind if it's a valid useable ent + } //else, return + else if ( trace.entityNum >= MAX_CLIENTS && !cgs.clientinfo[cg.snap->ps.clientNum].isAdmin/*cg.predictedPlayerState.persistant[PERS_CLASS] != PC_ADMIN*/ ) { + return; + } + + // if the player is in fog, don't show it + content = trap_CM_PointContents( trace.endpos, 0 ); + if ( content & CONTENTS_FOG ) { + return; + } + + // if the player is invisible, don't show it + if ( cg_entities[ trace.entityNum ].currentState.powerups & ( 1 << PW_INVIS ) && !cgs.clientinfo[cg.snap->ps.clientNum].isAdmin/*cg.snap->ps.persistant[PERS_CLASS] != PC_ADMIN*/ ) { + return; + } + + if ( cg.crosshairClientNum != trace.entityNum) { + infoStringCount = 0; + } + + } + + // update the fade timer + cg.crosshairClientNum = trace.entityNum; + cg.crosshairClientTime = cg.time; + + //CG_Printf( "Current ent num: %i\n", cg.crosshairClientNum ); +} + + +/* +===================== +CG_DrawCrosshairNames +===================== +*/ + +extern qboolean PM_PlayerCrouching ( int legsAnim ); + +static vec3_t playerMins = {-12, -12, -24}; //RPG-X : TiM - {-15, -15, -24} +static vec3_t playerMaxs = {12, 12, 32}; // {15, 15, 32} +static void CG_DrawCrosshairNames( void ) { + float *color; + char name[MAX_QPATH]; + int team; + centity_t *cent; + //vec4_t vecColor = { 0.0, 1.0, 0.0, 1.0 }; + int x, y; + qboolean tinyFont; + int drawFlags; + + if ( !cg_drawCrosshair.integer ) + { + return; + } + + //if ( cg.renderingThirdPerson ) + //{ + // return; + //} + + // scan the known entities to see if the crosshair is sighted on one + CG_ScanForCrosshairEntity(); + + // draw the name of the player being looked at + color = CG_FadeColor( cg.crosshairClientTime, 1000 ); + if ( !color ) + { + trap_R_SetColor( NULL ); + infoStringCount = 0; + return; + } + + color[3] *= 0.9; + + //If they're actively firing the tricorder + if( ( (cg.snap->ps.eFlags & EF_FIRING) && !(cg.snap->ps.eFlags & EF_ALT_FIRING) ) + && cg.snap->ps.weapon == WP_TRICORDER ) { + if(cg.crosshairClientNum != cg.predictedPlayerState.clientNum && cg.crosshairClientNum < MAX_CLIENTS ) { //ENTITYNUM_WORLD + + drawCrosshairName = qfalse; + + cent = &cg_entities[cg.crosshairClientNum]; + + if ( cent ) { + char *name = NULL; + char *rank = NULL; + char *race = NULL; + char *age = NULL; + char *pClass = NULL; + //vec3_t size; + float ht = 0; + float wt = 0; + //int health = 0; + char *weap = NULL; + char namestr[128]; + char rankstr[128]; + char racestr[128]; + char htstr[128]; + char wtstr[128]; + char weapstr[128]; + char agestr[128]; + char classstr[128]; + int i, irank; + int score = 0; + clientInfo_t *ci; + + for ( i = 0; i < cgs.maxclients; i++ ) { + if ( cg.scores[i].client == cg.crosshairClientNum ) { + score = cg.scores[i].score; + break; + } + } + + irank = score;//Q_log2( score ); + + ci = &cgs.clientinfo[cg.crosshairClientNum]; + //over-ride the color, since we can't get teams in this case + //use that good old LCARS yellow + color[0] = 0.694f;//0.9F;//R + color[1] = 0.816f;//0.7F;//G + color[2] = 1.0f;//0.0F;//B + color[3] *= 0.5; + + //vec3_t maxs, mins; + + //VectorCopy( crossEnt->maxs, maxs ); + //VectorCopy( crossEnt->mins, mins ); + //if ( crossEnt->client && crossEnt->NPC ) + //{//only use the standing height of the NPCs because people can't understand the complex dynamics of height in weight in a ceiling-installed anti-gravitic plating environment + // maxs[2] = crossEnt->client->standheight; + //} + //VectorSubtract(maxs, mins, size); + //ht = (maxs[2] - mins[2]) * 3.46875;//magic number + ht = ci->height * (float)BASE_HEIGHT; + //wt = VectorLength(size)*1.4;//magic number + wt = ci->weight * ci->height * (float)BASE_WEIGHT; + //if ( crossEnt->client && crossEnt->NPC ) + //{ + //if ( strstr( crossEnt->client->renderInfo.legsModelName, "female" ) || + // strstr( crossEnt->client->renderInfo.legsModelName, "seven" ) ) + //{//crewfemale, hazardfemale or seven of nine + if ( ci->gender == GENDER_FEMALE ) { + wt *= (float)FEMALE_OFFSET;//magic number, women are lighter than men + } + + if ( ci->race && ci->race[0] ) { + race = ci->race; + Com_sprintf( racestr, sizeof( racestr ), "%s: %s", "Race", race ); + //Q_strncpyz( race, racestr, sizeof( racestr) ); + } + + if ( ci->age && ci->age[0] ) { + age = ci->age; + Com_sprintf( agestr, sizeof( agestr ), "%s: %s", "Age", age ); + //Q_strncpyz( race, racestr, sizeof( racestr) ); + } + + //Com_Printf( "%i\n", ci->pClass ); + pClass = cgs.classData[ci->pClass].formalName; + /*switch ( ci->pClass ) { + case PC_ADMIN: + pClass = "Admin"; + break; + case PC_SECURITY: + pClass = "Security"; + break; + case PC_ALIEN: + pClass = "Alien"; + break; + case PC_COMMAND: + pClass = "Command"; + break; + case PC_SCIENCE: + pClass = "Science"; + break; + case PC_ENGINEER: + pClass = "Engineer"; + break; + case PC_ALPHAOMEGA22: + pClass = "Marine"; + break; + case PC_N00B: + pClass = "n00b"; + break; + case PC_NOCLASS: + default: + pClass = "Unknown"; + break; + }*/ + + if ( pClass ) { + Com_sprintf( classstr, sizeof(classstr), "%s: %s", "Class", pClass ); + } + + if ( cgs.classData[ci->pClass].showRanks/*ci->pClass != PC_ALIEN && ci->pClass != PC_NOCLASS*/ ) { + //rank = "Awesome"; //RankForNumber func needed + if ( cgs.ranksData[irank].formalName[0] ) { + rank = cgs.ranksData[irank].formalName; + Com_sprintf( rankstr, sizeof( rankstr ), "%s: %s", "Rank", rank ); + } + //Q_strncpyz( rank, rankstr, sizeof( rankstr ) ); + } + + if ( ci->name && ci->name[0] ) { + name = ci->name; + } + else { + name = "Data Not Available";//crossEnt->targetname; + } + + Com_sprintf( namestr, sizeof( namestr), "%s: %s", "Name", name ); + + if ( cent->currentState.weapon != WP_NULL_HAND /*&& cg_weapons[ cent->currentState.weapon ].item*/ ) + { + if ( cg_weapons[ cent->currentState.weapon ].item->pickup_name ) { + weap = cg_weapons[ cent->currentState.weapon ].item->pickup_name; + Com_sprintf( weapstr, sizeof( weapstr), "%s: %s", "Weapon", weap ); + } + } + + Com_sprintf( htstr, sizeof(htstr), "%s: %4.2f %s","Height", ht, HEIGHT_UNIT ); + Com_sprintf( wtstr, sizeof(wtstr), "%s: %4.2f %s","Weight", wt, WEIGHT_UNIT ); + + //Com_Printf("Name: %s, Rank: %s, Race: %s, Height: %s, Weight: %s, Weap: %s\n", namestr, rankstr, racestr, htstr, wtstr, weapstr ); + + CG_LabelViewEntity( cg.crosshairClientNum, cent->lerpOrigin, playerMins, playerMaxs, + name ? namestr : NULL, qfalse, color, + (cgs.clientinfo[cg.snap->ps.clientNum].isAdmin || cgs.classData[cg.snap->ps.persistant[PERS_CLASS]].isMedic) ? qtrue : qfalse, ci->health, + pClass ? classstr : NULL, + rank ? rankstr : NULL, + race ? racestr : NULL, + age ? agestr : NULL, + ht ? htstr : NULL, + wt ? wtstr : NULL, + weap ? weapstr : NULL); + } + else { + infoStringCount = 0; + } + } + else { + if ( (cg_entities[cg.crosshairClientNum].currentState.eType == ET_TRIC_STRING || cg_entities[cg.crosshairClientNum].currentState.eType == ET_MOVER_STR) && cgs.scannablePanels ) + { + entityState_t *eState; + vec3_t origin; + vec3_t mins, maxs; + char *renderString; + + eState = &cg_entities[cg.crosshairClientNum].currentState; + + color[0] = 0.694f;//0.9F;//R + color[1] = 0.816f;//0.7F;//G + color[2] = 1.0f;//0.0F;//B + color[3] *= 0.5; + + //TiM: Since dynamic brush ents seem to have no freaking origin in them, let's + // calc our own using the bounding box dimensions (At least we have those lol ) + VectorAverage( eState->origin2, eState->angles2, origin ); + //origin[2] = eState->origin2[2] - 24; + + //The algorithm needs the max and min dimensions to be symmetrical on either side + //of the origin. This set of random code does that. :) + VectorSubtract( origin, eState->origin2, mins ); + + VectorSet( maxs, Q_fabs( mins[0] ), Q_fabs( mins[1] ), Q_fabs( mins[2] ) ); + VectorScale( maxs, -1, mins ); + + if ( eState->time2 > 0 ) + renderString = (char *)CG_ConfigString( CS_TRIC_STRINGS + eState->time2 ); + else if ( eState->weapon > 0 && cgs.scannableStrings[eState->weapon-1][0] ) + renderString = cgs.scannableStrings[eState->weapon-1]; //subtracted since '0' is a valid cell value + else + renderString = ""; + + + CG_LabelViewEntity( cg.crosshairClientNum, origin, + mins, maxs, renderString, //cgs.tricStrings[eState->time2], + qfalse, color, + qfalse, 0, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL); + } + } + } + else + drawCrosshairName = qtrue; + + if ( !cg_drawCrosshairNames.integer || cg.crosshairClientNum > MAX_CLIENTS || !drawCrosshairName ) + { + return; + } + + //Now only draw team names + health if specifically wanted + Q_strncpyz (name, cgs.clientinfo[ cg.crosshairClientNum ].name, sizeof (name) ); + + // Draw in red if red team, blue if blue team + if (cgs.gametype >= GT_TEAM) + { + Q_CleanStr(name); + team = cgs.clientinfo[ cg.crosshairClientNum ].team; + if (team==TEAM_RED) + { + color[0] = colorRed[0]; + color[1] = colorRed[1]; + color[2] = colorRed[2]; + } + else + { + color[0] = colorBlue[0]; + color[1] = colorBlue[1]; + color[2] = colorBlue[2]; + } + } + else + { + color[0] = colorTable[CT_YELLOW][0]; + color[1] = colorTable[CT_YELLOW][1]; + color[2] = colorTable[CT_YELLOW][2]; + } + + if ( !cg_dynamicCrosshairNames.integer ) + { + x = 320; + y = 170; + + tinyFont = qfalse; + + drawFlags = UI_CENTER|UI_SMALLFONT; + } + else + { + vec3_t org; + centity_t *cent; + float x2, y2; + + cent = &cg_entities[ cg.crosshairClientNum ]; + + VectorCopy( cent->lerpOrigin, org ); + + if ( PM_PlayerCrouching( cent->currentState.legsAnim ) ) + org[2] += CROUCH_VIEWHEIGHT + 7; + else + org[2] += DEFAULT_VIEWHEIGHT + 7; + + CG_WorldCoordToScreenCoord( org, &x2, &y2, qfalse); + + x = (int)x2; + y = (int)y2; + + tinyFont = qtrue; + drawFlags = UI_CENTER|UI_BOTTOM|UI_TINYFONT; + } + + //FIXME: need health (&armor?) of teammates (if not TEAM_FREE) or everyone (if SPECTATOR) (or just crosshairEnt?) sent to me + if (cgs.clientinfo[ cg.snap->ps.clientNum ].team == TEAM_SPECTATOR + || cgs.classData[cgs.clientinfo[ cg.snap->ps.clientNum ].pClass].isMedic + || cgs.clientinfo[ cg.snap->ps.clientNum ].isAdmin ) /*|| cgs.clientinfo[ cg.snap->ps.clientNum ].pClass == PC_MEDIC*/ + {//if I'm a spectator, draw colored health of target under crosshair + CG_GetColorForHealth( cgs.clientinfo[ cg.crosshairClientNum ].health, cgs.clientinfo[ cg.crosshairClientNum ].armor, color ); + + y -= ( tinyFont ? TINYCHAR_HEIGHT : SMALLCHAR_HEIGHT ); + + UI_DrawProportionalString(x,y+(tinyFont ? TINYCHAR_HEIGHT+5 : SMALLCHAR_HEIGHT),va( "^7%i", cgs.clientinfo[ cg.crosshairClientNum ].health ), drawFlags,color); + } + + UI_DrawProportionalString(x,y, va("^7%s ^7(%i)", name, cg.crosshairClientNum), drawFlags, color); +} + + + +//============================================================================== + +/* +================= +CG_DrawSpectator +================= +*/ +static void CG_DrawSpectator(void) { +// CG_DrawBigString(320 - 9 * 8, 440, ingame_text[IGT_SPECTATOR], 1.0F); + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) + { + UI_DrawProportionalString(SCREEN_WIDTH/2, SCREEN_HEIGHT - ((BIGCHAR_HEIGHT * 1.50) * 2) , ingame_text[IGT_SPECTATOR], UI_BIGFONT|UI_CENTER, colorTable[CT_LTGOLD1]); + } + /*else if ( cg.snap->ps.eFlags&EF_ELIMINATED ) + { + UI_DrawProportionalString(SCREEN_WIDTH/2, SCREEN_HEIGHT - ((BIGCHAR_HEIGHT * 1.50) * 2) , ingame_text[IGT_TITLEELIMINATED], UI_BIGFONT|UI_CENTER, colorTable[CT_LTGOLD1]); + }*/ + if ( cgs.gametype == GT_TOURNAMENT ) { +// CG_DrawBigString(320 - 15 * 8, 460, ingame_text[IGT_WAITINGTOPLAY], 1.0F); + UI_DrawProportionalString(SCREEN_WIDTH/2, SCREEN_HEIGHT - (BIGCHAR_HEIGHT * 1.5), ingame_text[IGT_WAITINGTOPLAY], UI_BIGFONT|UI_CENTER, colorTable[CT_LTGOLD1]); + } + if ( cgs.gametype == GT_TEAM || cgs.gametype == GT_CTF ) { +// CG_DrawBigString(320 - 25 * 8, 460, ingame_text[IGT_USEDTEAMMENU], 1.0F); + UI_DrawProportionalString(SCREEN_WIDTH/2, SCREEN_HEIGHT - (BIGCHAR_HEIGHT * 1.5), ingame_text[IGT_USEDTEAMMENU], UI_BIGFONT|UI_CENTER, colorTable[CT_LTGOLD1]); + } +} + +/* +================= +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 = ( VOTE_TIME - ( cg.time - cgs.voteTime ) ) / 1000; + if ( sec < 0 ) { + sec = 0; + } + s = va("%s(%i):%s %s(F1):%i %s(F2):%i", ingame_text[IGT_VOTE],sec, cgs.voteString,ingame_text[IGT_YES], cgs.voteYes,ingame_text[IGT_NO] ,cgs.voteNo); + +// CG_DrawSmallStringColor( 0, 58, s, colorTable[CT_YELLOW] ); + UI_DrawProportionalString( 0, 58, s, UI_SMALLFONT, colorTable[CT_YELLOW]); +} + +/* +================= +CG_DrawIntermission +================= +*/ +static void CG_DrawIntermission( void ) { + if (0)// cgs.gametype == GT_SINGLE_PLAYER ) + { + CG_DrawCenterString(); + return; + } + + cg.scoreFadeTime = cg.time; + CG_DrawScoreboard(); +} + +/* +================= +CG_DrawAbridgedObjective +================= +*/ +static void CG_DrawAbridgedObjective(void) +{ + int i,pixelLen,x,y; + + for (i=0;ips.pm_flags & PMF_FOLLOW) ) { + return qfalse; + } + color[0] = 1; + color[1] = 1; + color[2] = 1; + color[3] = 1; + + y = 16; + + UI_DrawProportionalString((SCREEN_WIDTH/2), y, ingame_text[IGT_FOLLOWING], UI_BIGFONT|UI_CENTER, colorTable[CT_LTGOLD1]); + + name = cgs.clientinfo[ cg.snap->ps.clientNum ].name; + + y += (BIGCHAR_HEIGHT * 1.25); + UI_DrawProportionalString( (SCREEN_WIDTH/2), 40, name, UI_BIGFONT|UI_CENTER, color); + + return qtrue; +} + + + +/* +================= +CG_DrawAmmoWarning +================= +RPG-X | Marcin | 30/12/2008 +Don't!!! +*/ +static void CG_DrawAmmoWarning( void ) +{ + return; +/* + const char *s; + + if ( cg_drawAmmoWarning.integer == 0 ) + { + return; + } + + if ( !cg.lowAmmoWarning ) + { + return; + } + + if ( cg.lowAmmoWarning >= 2 ) + { + s = ingame_text[IGT_OUTOFAMMO]; + } else + { + s = ingame_text[IGT_LOWAMMO]; + } + + UI_DrawProportionalString(320, 64, s, UI_SMALLFONT | UI_CENTER, colorTable[CT_LTGOLD1]); */ + +} + +/* +================= +CG_DrawWarmup +================= +*/ +extern void CG_AddGameModNameToGameName( char *gamename ); +static void CG_DrawWarmup( void ) { + int w; + int sec; + int i; + clientInfo_t *ci1, *ci2; + int cw; + const char *s; + + sec = cg.warmup; + if ( !sec ) { + return; + } + + if ( sec < 0 ) { + s = ingame_text[IGT_WAITINGFORPLAYERS]; +// w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + w = UI_ProportionalStringWidth(s,UI_BIGFONT); +// CG_DrawBigString(320 - w / 2, 40, s, 1.0F); + UI_DrawProportionalString(320 - w / 2, 40, s, UI_BIGFONT, colorTable[CT_LTGOLD1]); + + cg.warmupCount = 0; + return; + } + + if (cgs.gametype == GT_TOURNAMENT) { + // find the two active players + ci1 = NULL; + ci2 = NULL; + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_FREE ) { + if ( !ci1 ) { + ci1 = &cgs.clientinfo[i]; + } else { + ci2 = &cgs.clientinfo[i]; + } + } + } + + if ( ci1 && ci2 ) { + s = va( "%s vs %s", ci1->name, ci2->name ); +// w = CG_DrawStrlen( s ); + w = UI_ProportionalStringWidth(s,UI_BIGFONT); + + if ( w > 640 / BIGCHAR_WIDTH ) { + cw = 640 / w; + } else { + cw = BIGCHAR_WIDTH; + } +// CG_DrawStringExt( 320 - w * cw/2, 20,s, colorWhite, +// qfalse, qtrue, cw, (int)(cw * 1.5), 0 ); + UI_DrawProportionalString( (SCREEN_WIDTH/2), 20,s, UI_BIGFONT|UI_CENTER, colorTable[CT_LTGOLD1]); + + } + } else { + char gamename[1024]; + + if ( cgs.gametype == GT_FFA ) { + s = ingame_text[IGT_GAME_FREEFORALL]; + } else if ( cgs.gametype == GT_TEAM ) { + s =ingame_text[IGT_GAME_TEAMHOLOMATCH]; + } else if ( cgs.gametype == GT_CTF ) { + s = ingame_text[IGT_GAME_CAPTUREFLAG]; + } else { + s = ""; + } + + Q_strncpyz( gamename, s, sizeof(gamename) ); + + CG_AddGameModNameToGameName( gamename ); + + w = UI_ProportionalStringWidth(s,UI_BIGFONT); + + if ( w > 640 / BIGCHAR_WIDTH ) { + cw = 640 / w; + } else { + cw = BIGCHAR_WIDTH; + } + + UI_DrawProportionalString((SCREEN_WIDTH/2) , 20,gamename, UI_BIGFONT|UI_CENTER, colorTable[CT_LTGOLD1]); + } + + sec = ( sec - cg.time ) / 1000; + if ( sec < 0 ) { + sec = 0; + } + s = va( "%s: %i",ingame_text[IGT_STARTSIN], sec + 1 ); + if ( sec != cg.warmupCount ) { + cg.warmupCount = sec; + switch ( sec ) { + case 0: + trap_S_StartLocalSound( cgs.media.count1Sound, CHAN_ANNOUNCER ); + break; + case 1: + trap_S_StartLocalSound( cgs.media.count2Sound, CHAN_ANNOUNCER ); + break; + case 2: + trap_S_StartLocalSound( cgs.media.count3Sound, CHAN_ANNOUNCER ); + break; + default: + break; + } + } + switch ( cg.warmupCount ) { + case 0: + cw = 28; + break; + case 1: + cw = 24; + break; + case 2: + cw = 20; + break; + default: + cw = 16; + break; + } + +// w = CG_DrawStrlen( s ); +// CG_DrawStringExt( 320 - w * cw/2, 70, s, colorWhite, +// qfalse, qtrue, cw, (int)(cw * 1.5), 0 ); + + w = UI_ProportionalStringWidth(s,UI_BIGFONT); + UI_DrawProportionalString( (SCREEN_WIDTH/2), 70, s, UI_BIGFONT|UI_CENTER, colorTable[CT_LTGOLD1]); + +} + +/* +================ +CG_DrawZoomMask + +================ +*/ +static void CG_DrawZoomMask( void ) +{ + float amt = 1, size, /*val,*/ start_x, start_y; + int width, height, i; + vec4_t color1; + int x, y; + + /*if ( cg.snap->ps.persistant[PERS_CLASS] == PC_NOCLASS + || cg.snap->ps.persistant[PERS_CLASS] != PC_SECURITY + && cg.snap->ps.persistant[PERS_CLASS] != PC_ALPHAOMEGA22 + && cg.snap->ps.persistant[PERS_CLASS] != PC_ADMIN ) + {//in a class-based game, only the sniper can zoom + cg.zoomed = qfalse; + cg.zoomLocked = qfalse; + return; + }*/ + //TiM: New system. :) Base zoom on current active weapon. :) + if ( !(cg.snap->ps.weapon == WP_COMPRESSION_RIFLE || cg.snap->ps.weapon == WP_TR116) ) + { + cg.zoomed = qfalse; + cg.zoomLocked = qfalse; + return; + } + + // Calc where to place the zoom mask...all calcs are based off of a virtual 640x480 screen + size = cg_viewsize.integer; + + width = 640 * size * 0.01; + width &= ~1; + + height = 480 * size * 0.01; + height &= ~1; + + start_x = ( 640 - width ) * 0.5; + start_y = ( 480 - height ) * 0.5; + + if ( cg.zoomed ) + { + // Smoothly fade in..Turn this off for now since the zoom is set to snap to 30% or so...fade looks a bit weird when it does that + if ( cg.time - cg.zoomTime <= ZOOM_OUT_TIME ) { + amt = ( cg.time - cg.zoomTime ) / ZOOM_OUT_TIME; + } + + // Fade mask in + for ( i = 0; i < 4; i++ ) { + color1[i] = amt; + } + + // Set fade color + trap_R_SetColor( color1 ); + + if ( cg.snap->ps.weapon == WP_TR116 ) { + static int TR116LoopTime = 0; + + //Loop the whirring sight sound + if ( TR116LoopTime < cg.time ) + { + trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_LOCAL, cgs.media.tr116Whir ); + TR116LoopTime = cg.time + 900; + } + + CG_DrawPic( start_x, start_y, width, height, cgs.media.zoomMask116Shader ); + + //if we're zoomed over a potential target, start flashing the red crosshair + if ( cg.crosshairClientNum != cg.snap->ps.clientNum + && cg.crosshairClientNum < MAX_CLIENTS ) + { + vec4_t alphaColor; + float amt; + + if ( cg.time > zoomFlashTime ) { + zoomFlashTime = cg.time + 800; + trap_S_StartLocalSound( cgs.media.tr116Chirp, CHAN_LOCAL_SOUND ); + } + + amt = ( ( zoomFlashTime % cg.time ) / 500.0f ); + amt = Com_Clamp( 0.0, 1.0, amt ); + + //Com_Printf( S_COLOR_RED "Ratio: %f\n", amt ); + + VectorSet( alphaColor, 1, 1, 1 ); + alphaColor[3] = amt; + + trap_R_SetColor( alphaColor ); + //======================================== + + CG_DrawPic( 256, 176, 128, 128, cgs.media.zoomGlow116Shader ); + trap_R_SetColor( color1 ); + } + else + { + zoomFlashTime = 0; + } + } + else { + CG_DrawPic( start_x, start_y, width, height, cgs.media.zoomMaskShader ); + } + + /* RPG-X - TiM : Since the rifle's view doesn't really account for these elements, toss 'em + start_x = 210; + start_y = 80; + + CG_DrawPic( 320 + start_x, 241, 35, -170, cgs.media.zoomBarShader); + CG_DrawPic( 320 - start_x, 241, -35, -170, cgs.media.zoomBarShader); + CG_DrawPic( 320 + start_x, 239, 35, 170, cgs.media.zoomBarShader); + CG_DrawPic( 320 - start_x, 239, -35, 170, cgs.media.zoomBarShader); + + // Calculate a percent and clamp it + val = 26 - ( cg_fov.value - cg_zoomFov.value ) / ( cg_fov.value - MAX_ZOOM_FOV ) * 26; + + if ( val > 17.0f ) + val = 17.0f; + else if ( val < 0.0f ) + val = 0.0f; + + // pink + color1[0] = 0.85f; + color1[1] = 0.55f; + color1[2] = 0.75f; + color1[3] = 1.0f; + + CG_DrawPic( 320 + start_x + 12, 245, 10, 108, cgs.media.zoomInsertShader ); + CG_DrawPic( 320 + start_x + 12, 235, 10, -108, cgs.media.zoomInsertShader ); + CG_DrawPic( 320 - start_x - 12, 245, -10, 108, cgs.media.zoomInsertShader ); + CG_DrawPic( 320 - start_x - 12, 235, -10, -108, cgs.media.zoomInsertShader ); + + trap_R_SetColor( color1 ); + i = ((int)val) * 6; + + CG_DrawPic( 320 + start_x + 10, 230 - i, 12, 5, cgs.media.ammoslider ); + CG_DrawPic( 320 + start_x + 10, 251 + i, 12, -5, cgs.media.ammoslider ); + CG_DrawPic( 320 - start_x - 10, 230 - i, -12, 5, cgs.media.ammoslider ); + CG_DrawPic( 320 - start_x - 10, 251 + i, -12, -5, cgs.media.ammoslider ); + */ + + //yellow + if ( cg.snap->ps.weapon == WP_TR116 ) { + color1[0] = 0.886f; + color1[1] = 0.749f; + color1[2] = 0.0f; + color1[3] = 0.60f; + } + else { // red + color1[0] = 1.0f; + color1[1] = 0.0f; + color1[2] = 0.0f; + color1[3] = 0.60f; + } + + // Convert zoom and view axis into some numbers to throw onto the screen + if ( cg.snap->ps.weapon == WP_TR116 ) { + x = 74; + y = 340; + } + else { + x = 468; + y = 300; + } + + trap_R_SetColor( color1 ); + CG_DrawNumField( x, y, 5, cg_zoomFov.value * 1000 + 9999, 18, 10 ,NUM_FONT_BIG ); //100 + CG_DrawNumField( x, y+20, 5, cg.refdef.viewaxis[0][0] * 9999 + 20000, 18, 10,NUM_FONT_BIG ); + CG_DrawNumField( x, y+40, 5, cg.refdef.viewaxis[0][1] * 9999 + 20000, 18, 10,NUM_FONT_BIG ); + CG_DrawNumField( x, y+60, 5, cg.refdef.viewaxis[0][2] * 9999 + 20000, 18, 10,NUM_FONT_BIG ); + + /* + // Is it time to draw the little max zoom arrows? + if ( val < 0.2f ) + { + amt = sin( cg.time * 0.03 ) * 0.5 + 0.5; + color1[0] = 0.592156f * amt; + color1[1] = 0.592156f * amt; + color1[2] = 0.850980f * amt; + color1[3] = 1.0f * amt; + + trap_R_SetColor( color1 ); + + CG_DrawPic( 320 + start_x, 240 - 6, 16, 12, cgs.media.zoomArrowShader ); + CG_DrawPic( 320 - start_x, 240 - 6, -16, 12, cgs.media.zoomArrowShader ); + }*/ + } + else + { + if ( cg.time - cg.zoomTime <= ZOOM_OUT_TIME ) + { + amt = 1.0f - ( cg.time - cg.zoomTime ) / ZOOM_OUT_TIME; + + // Fade mask away + for ( i = 0; i < 4; i++ ) { + color1[i] = amt; + } + + trap_R_SetColor( color1 ); + if ( cg.snap->ps.weapon == WP_TR116 ) { + CG_DrawPic( start_x, start_y, width, height, cgs.media.zoomMask116Shader ); + } + else { + CG_DrawPic( start_x, start_y, width, height, cgs.media.zoomMaskShader ); + } + } + } +} + +//================================================================================== + +static char *AfterSpace( char *p ) +{ + while (*p && *p != ' ') { + ++p; + } + + return p; +} + +/* +===================== +CG_Drawcg.adminMsg +RPG-X | Phenix | 08/06/2005 +RPG-X | Marcin | 30/12/2008 + +Now I'm going to kill you Phenix!!!! +===================== +*/ +static void CG_DrawAdminMsg( void ) { + float y; + int t; + int i, msgRow, msgCol; + int biggestW, w; + char message[35][45]; + char *thisMessage; + char *p, *currRow; + static vec4_t color; + static vec4_t Boxcolor; + + y = 460; + + //Nothing to display + if ( cg.adminMsgTime < cg.time ) + { + return; + } + + //No message! + if ( cg.adminMsgMsg[0] == '\0' ) + { + return; + } + + //Colour Fade. + t = cg.adminMsgTime - cg.time; + + // fade out + if (t < 500) { + color[3] = t * 1.0/500; + } else { + color[3] = 1.0; + } + color[0] = color[1] = color[2] = 1; + + Boxcolor[0] = 0.016; + Boxcolor[1] = 0.055; + Boxcolor[2] = 0.170; + /* + CT_VDK_PURPLE + (New values are halfed + Boxcolor[0] = 0.031; + Boxcolor[1] = 0.110; + Boxcolor[2] = 0.341; + */ + Boxcolor[3] = color[3]; + + if ( !color ) { + return; + } + + trap_R_SetColor( color ); + + i = 0; + + msgRow = 0; + msgCol = 0; + p = cg.adminMsgMsg; + currRow = p; + + while ( qtrue ) { + if ( !*p ) { + break; + } + + if ( NextWordEndsHere( p ) - currRow > 43 ) { //we need to wrap... + message[msgRow][msgCol] = '\0'; + currRow = p++; + ++msgRow, msgCol = 0; + continue; + } + + message[msgRow][msgCol] = *p; + ++p, ++msgCol; + } + + message[msgRow][msgCol] = '\0'; + ++msgRow; + + /* Sorry but this code really didn't work Phenix ... memory errors. + for (msgRow = 0; msgRow < 35; msgRow++) + { + if (cg.adminMsgMsg[i] == '\0') + { + break; + } + + for (msgCol = 0; msgCol < 45; msgCol++) + { + if (cg.adminMsgMsg[i] == '\0') + { + break; + } + + if (cg.adminMsgMsg[i] == '\\') + { + i++; + break; + } + + if ((msgCol >= 30) && (cg.adminMsgMsg[i] == ' ')) + { + i++; + break; + } + + if (msgCol == 44) { + message[msgRow][msgCol] = '-'; + } else { + message[msgRow][msgCol] = cg.adminMsgMsg[i]; + i++; + } + } + } + */ + + biggestW = 0; + for (i = 0; i < msgRow; i++) + { + thisMessage = va("%s", message[i]); + w = UI_ProportionalStringWidth(thisMessage, UI_SMALLFONT); + + if (w > biggestW) + { + biggestW = w; + } + } + + CG_FillRect( 640 - (biggestW + 22), y - (((SMALLCHAR_HEIGHT + 2) * msgRow) + 2), biggestW + 4, ((SMALLCHAR_HEIGHT + 2) * msgRow) + 4, Boxcolor ); + + for (i = (msgRow - 1); i >= 0; i--) + { + y -= (SMALLCHAR_HEIGHT + 2); + + //memset(&thisMessage, 0, sizeof(thisMessage)); + thisMessage = va("%s", message[i]); + UI_DrawProportionalString(640 - (biggestW + 20), y, thisMessage, UI_SMALLFONT, color); + } + + + trap_R_SetColor( NULL ); + +} + +/* +================= +CG_Draw2D +================= +*/ +static void CG_Draw2D( void ) { + int i; + + //TiM : Testing this API function... + //trap_R_SetColor( colorTable[ CT_RED ] ); + //trap_R_DrawStretchPic( 100, 100, 800, 600, 0, 0, 0.5, 0.5, cgs.media.charsetPropB ); + + // if we are taking a levelshot for the menu, don't draw anything + if ( cg.levelShot ) { + return; + } + + if ( cg_draw2D.integer == 0 ) { + return; + } + + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { +#ifndef FINAL_BUILD + CG_DrawUpperRight(); +#endif + CG_DrawIntermission(); + return; + } + + if ( !cg.renderingThirdPerson ) + { + CG_DrawZoomMask(); + } + + //RPG-X: RedTechie - Keep Lagometer on the botum always + + cgs.widescreen.state = WIDESCREEN_RIGHT; + CG_DrawLagometer(); + cgs.widescreen.state = WIDESCREEN_NONE; + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR /*|| (cg.snap->ps.eFlags&EF_ELIMINATED)*/ ) { +// CG_DrawSpectator(); + CG_DrawCrosshair(); + CG_DrawCrosshairNames(); + } else { + cgs.widescreen.state = WIDESCREEN_LEFT; + CG_DrawStatusBar(); //RPG-X: RedTechie - We want health displayed when dead + // don't draw any status if dead + if ( cg.snap->ps.stats[STAT_HEALTH] > 1 ) { //RPG-X: RedTechie - No weapons at health 1 (you die at health 1 now) + //CG_DrawStatusBar(); + CG_DrawAmmoWarning(); + + cgs.widescreen.state = WIDESCREEN_NONE; + CG_DrawCrosshair(); + + cgs.widescreen.state = WIDESCREEN_CENTER; + CG_DrawCrosshairNames(); + + cgs.widescreen.state = WIDESCREEN_LEFT; + CG_DrawWeaponSelect(); + + cgs.widescreen.state = WIDESCREEN_RIGHT; + CG_DrawHoldableItem(); + CG_DrawReward(); + CG_DrawAbridgedObjective(); + + cgs.widescreen.state = WIDESCREEN_NONE; + } + if ( cgs.gametype >= GT_TEAM ) { + CG_DrawTeamInfo(); + } + cgs.widescreen.state = WIDESCREEN_NONE; + } + + if (cg.showObjectives) + { + CG_DrawObjectiveInformation(); + } + + CG_DrawVote(); + + //RPG-X: RedTechie - Moved above others to keep on the bottum + //CG_DrawLagometer(); + + CG_DrawUpperRight(); + + CG_DrawLowerRight(); + + CG_DrawLowerLeft(); + + //RPG-X | Phenix | 08/06/2005 + cgs.widescreen.state = WIDESCREEN_CENTER; + CG_DrawAdminMsg(); + cgs.widescreen.state = WIDESCREEN_NONE; + + cgs.widescreen.state = WIDESCREEN_CENTER; + if ( !CG_DrawFollow() ) { + CG_DrawWarmup(); + } + cgs.widescreen.state = WIDESCREEN_NONE; + + // don't draw center string if scoreboard is up + cgs.widescreen.state = WIDESCREEN_CENTER; + if ( !CG_DrawScoreboard() ) { + CG_DrawCenterString(); + } + cgs.widescreen.state = WIDESCREEN_NONE; + + // kef -- need the "use TEAM menu to play" message to draw on top of the bottom bar of scoreboard + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR /*|| (cg.snap->ps.eFlags&EF_ELIMINATED)*/ ) + { + cgs.widescreen.state = WIDESCREEN_CENTER; + CG_DrawSpectator(); + cgs.widescreen.state = WIDESCREEN_NONE; + } + + //TiM - Draw teh fl4r3s + + for (i = 0; i < MAX_LENS_FLARES; i++) { + if ( lensFlare[i].qfull ) + CG_DrawLensFlare( &lensFlare[i] ); + + if ( lensFlare[i].upTime + lensFlare[i].holdTime + lensFlare[i].downTime > 0 && + cg.time > lensFlare[i].startTime + lensFlare[i].upTime + lensFlare[i].holdTime + lensFlare[i].downTime ) + lensFlare[i].qfull = qfalse; + } + +} + +#ifdef XTRA +void CG_MotionBlur(void) { + //motionblurDot_t *dot; + //vec3_t pos, axis[3]; + //int i; + + + /*if ( !cg.snap->ps.powerups[PW_BOOST] && cg.snap->ps.timers[tmZanzoken] < 1 && !cg.snap->ps.timers[tmTransform]) { + cg.refdef.rdflags &= ~RDF_MOTIONBLUR; + + + //for ( i = 0; i < MAX_MOTIONBLURDOTS; i++ ) { + // cg_motionblurDots[i].active = qfalse; + //} + + + return; + }*/ + + + cg.refdef.rdflags |= RDF_MOTIONBLUR; + + /* + // Destroy dots over lifetime + for ( i = 0; i < MAX_MOTIONBLURDOTS; i++ ) { + dot = &cg_motionblurDots[i]; + + if ( dot->lifeTime + dot->startTime < cg.time ) { + dot->active = qfalse; + } + } + + // Create new dots + for ( i = 0; i < MAX_MOTIONBLURDOTS; i++ ) { + dot = &cg_motionblurDots[i]; + + if ( dot->active ) + continue; + + VectorCopy( cg.predictedPlayerEntity.lerpOrigin, pos ); + VectorNormalize2( cg.predictedPlayerState.velocity, axis[0] ); + VectorMA( pos, 300, axis[0], pos ); + RotateAroundDirection( axis, crandom() * 360 ); + VectorMA( pos, 120, axis[2], pos ); + + memset( &(dot->refEnt), 0, sizeof(refEntity_t)); + dot->refEnt.reType = RT_SPRITE; + dot->refEnt.radius = 2; + dot->refEnt.customShader = cgs.media.whiteShader; + dot->refEnt.shaderRGBA[0] = 255; + dot->refEnt.shaderRGBA[1] = 255; + dot->refEnt.shaderRGBA[2] = 255; + dot->refEnt.shaderRGBA[3] = 128; + VectorCopy( pos, dot->refEnt.origin ); + + dot->lifeTime = 250 + crandom() * 100; + dot->startTime = cg.time + crandom() * 150; + dot->active = qtrue; + } + + // Render dots + for ( i = 0; i < MAX_MOTIONBLURDOTS; i++ ) { + dot = &cg_motionblurDots[i]; + if ( dot->startTime > cg.time ) + continue; + + trap_R_AddRefEntityToScene( &(dot->refEnt)); + } + */ +} +#endif //XTRA + + +/* +===================== +CG_DrawActive + +Perform all drawing needed to completely fill the screen +===================== +*/ +void CG_DrawActive( stereoFrame_t stereoView ) { + float separation; + vec3_t baseOrg; + + // optionally draw the info screen instead + if ( !cg.snap ) { + CG_DrawInformation(); + return; + } + + //vectors needed for tricorder + AngleVectors (cg.refdefViewAngles, vfwd, vright, vup); + VectorCopy( vfwd, vfwd_n ); + VectorCopy( vright, vright_n ); + VectorCopy( vup, vup_n ); + VectorNormalize( vfwd_n ); + VectorNormalize( vright_n ); + VectorNormalize( vup_n ); + + // optionally draw the tournement scoreboard instead + if ( (cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR /*|| (cg.snap->ps.eFlags&EF_ELIMINATED)*/)&& + ( cg.snap->ps.pm_flags & PMF_SCOREBOARD ) ) { + CG_DrawTourneyScoreboard(); + return; + } + + switch ( stereoView ) { + case STEREO_CENTER: + separation = 0; + break; + case STEREO_LEFT: + separation = -cg_stereoSeparation.value / 2; + break; + case STEREO_RIGHT: + separation = cg_stereoSeparation.value / 2; + break; + default: + separation = 0; + CG_Error( "CG_DrawActive: Undefined stereoView" ); + } + + + // clear around the rendered view if sized down + CG_TileClear(); + + // offset vieworg appropriately if we're doing stereo separation + VectorCopy( cg.refdef.vieworg, baseOrg ); + if ( separation != 0 ) { + VectorMA( cg.refdef.vieworg, -separation, cg.refdef.viewaxis[1], cg.refdef.vieworg ); + } + + #ifdef XTRA + CG_MotionBlur(); + #endif// XTRA + + // draw 3D view + 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/cgame/cg_drawtools.c b/cgame/cg_drawtools.c new file mode 100644 index 0000000..e69fcf4 --- /dev/null +++ b/cgame/cg_drawtools.c @@ -0,0 +1,1878 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_drawtools.c -- helper functions called by cg_draw, cg_scoreboard, cg_info, etc +#include "cg_local.h" + +#define CHARMAX 256 + +static qboolean CG_IsWidescreen( void ) +{ + if ( cg_handleWidescreen.integer && cgs.widescreen.ratio && cgs.widescreen.state != WIDESCREEN_NONE ) + return qtrue; + + return qfalse; +} + +/* +================ +CG_AdjustFrom640 + +Adjusted for resolution and screen aspect ratio +================ +*/ +void CG_AdjustFrom640( float *x, float *y, float *w, float *h ) { +//TiM - huh.... looks like the were looking into widescreens. +//only offsetting the X value isn't good enough tho +#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; + + //TiM - handle widescreens + if ( CG_IsWidescreen() ) + { + *w *= cgs.widescreen.ratio; + + if ( cgs.widescreen.state == WIDESCREEN_RIGHT ) + *x = (*x * cgs.widescreen.ratio) + (cgs.widescreen.bias*2);//*x = (*x+*w) - (*w * cgs.widescreen.ratio); + else if ( cgs.widescreen.state == WIDESCREEN_CENTER ) + *x = (*x * cgs.widescreen.ratio) + (cgs.widescreen.bias); + else + *x *= cgs.widescreen.ratio; + } +} + +/* +================ +CG_FillRect + +Coordinates are 640*480 virtual values +================= +*/ +void CG_FillRect( float x, float y, float width, float height, const float *color ) { + trap_R_SetColor( color ); + + CG_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cgs.media.whiteShader ); + + trap_R_SetColor( NULL ); +} + + + +/* +================ +CG_DrawPic + +Coordinates are 640*480 virtual values +================= +*/ +void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ) { + CG_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + +/* +================ +CG_DrawStretchPic + +TiM: Made so we can use +s1 - t2 as parameters as well :) + +Coordinates are 640*480 virtual values +================= +*/ +void CG_DrawStretchPic( float x, float y, float width, float height, float s1, float t1, float s2, float t2, qhandle_t hShader ) { + CG_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawStretchPic( x, y, width, height, s1, t1, s2, t2, hShader ); +} + +/* +=============== +CG_DrawChar + +Coordinates and size in 640*480 virtual screen size +=============== +*/ +void CG_DrawChar( int x, int y, int width, int height, int ch ) { + int row, col; + float frow, fcol; + float size,size2; + float ax, ay, aw, ah; + + ch &= 255; + + if ( ch == ' ' ) { + return; + } + + ax = x; + ay = y; + aw = width; + ah = height; + CG_AdjustFrom640( &ax, &ay, &aw, &ah ); + + row = ch>>4; + col = ch&15; + + + frow = row*0.0625; + fcol = col*0.0625; + size = 0.03125; + size2 = 0.0625; + + trap_R_DrawStretchPic( ax, ay, aw, ah, + fcol, frow, + fcol + size, frow + size2, + cgs.media.charsetShader ); + +} + + +/* +================== +CG_DrawStringExt + +Draws a multi-colored string with a drop shadow, optionally forcing +to a fixed color. + +Coordinates are at 640 by 480 virtual resolution +================== +*/ +void CG_DrawStringExt( int x, int y, const char *string, const float *setColor, + qboolean forceColor, qboolean shadow, int charWidth, int charHeight, int maxChars ) { + vec4_t color; + const char *s; + int xx; + int cnt; + + if (maxChars <= 0) + maxChars = 32767; // do them all! + + // draw the drop shadow + if (shadow) { + color[0] = color[1] = color[2] = 0; + color[3] = setColor[3]; + trap_R_SetColor( color ); + s = string; + xx = x; + cnt = 0; + while ( *s && cnt < maxChars) { + if ( Q_IsColorString( s ) ) { + s += 2; + continue; + } + CG_DrawChar( xx + 2, y + 2, charWidth, charHeight, *s ); + cnt++; + xx += charWidth; + s++; + } + } + + // draw the colored text + s = string; + xx = x; + cnt = 0; + trap_R_SetColor( setColor ); + while ( *s && cnt < maxChars) { + if ( Q_IsColorString( s ) ) { + if ( !forceColor ) { + memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color ) ); + color[3] = setColor[3]; + trap_R_SetColor( color ); + } + s += 2; + continue; + } + CG_DrawChar( xx, y, charWidth, charHeight, *s ); + xx += charWidth; + cnt++; + s++; + } + trap_R_SetColor( NULL ); +} + +void CG_DrawBigString( int x, int y, const char *s, float alpha ) { + float color[4]; + + color[0] = color[1] = color[2] = 1.0; + color[3] = alpha; + CG_DrawStringExt( x, y, s, color, qfalse, qtrue, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, 0 ); +} + +void CG_DrawBigStringColor( int x, int y, const char *s, vec4_t color ) { + CG_DrawStringExt( x, y, s, color, qtrue, qtrue, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, 0 ); +} + +void CG_DrawSmallString( int x, int y, const char *s, float alpha ) { + float color[4]; + + color[0] = color[1] = color[2] = 1.0; + color[3] = alpha; + CG_DrawStringExt( x, y, s, color, qfalse, qfalse, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); +} + +void CG_DrawSmallStringColor( int x, int y, const char *s, vec4_t color ) { + CG_DrawStringExt( x, y, s, color, qtrue, qfalse, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); +} + +/* +================= +CG_DrawStrlen + +Returns character count, skiping color escape codes +================= +*/ +int CG_DrawStrlen( const char *str ) { + const char *s = str; + int count = 0; + + while ( *s ) { + if ( Q_IsColorString( s ) ) { + s += 2; + } else { + count++; + s++; + } + } + + return count; +} + +/* +============= +CG_TileClearBox + +This repeats a 64*64 tile graphic to fill the screen around a sized down +refresh window. +============= +*/ +static void CG_TileClearBox( int x, int y, int w, int h, qhandle_t hShader ) { + float s1, t1, s2, t2; + + s1 = x / 64; + t1 = y / 64; + s2 = (x+w) / 64; + t2 = (y+h) / 64; + trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, hShader ); +} + + + +/* +============== +CG_TileClear + +Clear around a sized down screen +============== +*/ +void CG_TileClear( void ) { + int top, bottom, left, right; + int w, h; + + w = cgs.glconfig.vidWidth; + h = cgs.glconfig.vidHeight; + + if ( cg.refdef.x == 0 && cg.refdef.y == 0 && + cg.refdef.width == w && cg.refdef.height == h ) { + return; // full screen rendering + } + + top = cg.refdef.y; + bottom = top + cg.refdef.height-1; + left = cg.refdef.x; + right = left + cg.refdef.width-1; + + // clear above view screen + CG_TileClearBox( 0, 0, w, top, cgs.media.backTileShader ); + + // clear below view screen + CG_TileClearBox( 0, bottom, w, h - bottom, cgs.media.backTileShader ); + + // clear left of view screen + CG_TileClearBox( 0, top, left, bottom - top + 1, cgs.media.backTileShader ); + + // clear right of view screen + CG_TileClearBox( right, top, w - right, bottom - top + 1, cgs.media.backTileShader ); +} + + + +/* +================ +CG_FadeColor +================ +*/ +float *CG_FadeColor( int startMsec, int totalMsec ) { + static vec4_t color; + int t; + + if ( startMsec == 0 ) { + return NULL; + } + + t = cg.time - startMsec; + + if ( t >= totalMsec ) { + return NULL; + } + + // fade out + if ( totalMsec - t < FADE_TIME ) { + color[3] = ( totalMsec - t ) * 1.0/FADE_TIME; + } else { + color[3] = 1.0; + } + color[0] = color[1] = color[2] = 1; + + return color; +} + + +/* +================ +CG_TeamColor +================ +*/ +float *CG_TeamColor( int team ) { + static vec4_t red = {1, 0.2, 0.2, 1}; + static vec4_t blue = {0.2, 0.2, 1, 1}; + static vec4_t other = {1, 1, 1, 1}; + static vec4_t spectator = {0.7, 0.7, 0.7, 1}; + + switch ( team ) { + case TEAM_RED: + return red; + case TEAM_BLUE: + return blue; + case TEAM_SPECTATOR: + return spectator; + default: + return other; + } +} + + + +/* +================= +CG_GetColorForHealth +================= +*/ +void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ) { + int count; + int max; + + // calculate the total points of damage that can + // be sustained at the current health / armor level + if ( health <= 0 ) { + VectorClear( hcolor ); // black + hcolor[3] = 1; + return; + } + count = armor; + // kef -- FIXME ...not sure what's happening here, but a divide-by-zero would be bad + max = health;// * ARMOR_PROTECTION / ( 1.0 - ARMOR_PROTECTION ); + if ( max < count ) { + count = max; + } + health += count; + + // set the color based on health + hcolor[0] = 1.0; + hcolor[3] = 1.0; + if ( health >= 100 ) { + hcolor[2] = 1.0; + } else if ( health < 66 ) { + hcolor[2] = 0; + } else { + hcolor[2] = ( health - 66 ) / 33.0; + } + + if ( health > 60 ) { + hcolor[1] = 1.0; + } else if ( health < 30 ) { + hcolor[1] = 0; + } else { + hcolor[1] = ( health - 30 ) / 30.0; + } +} + +/* +================= +CG_ColorForHealth +================= +*/ +void CG_ColorForHealth( vec4_t hcolor ) { + + CG_GetColorForHealth( cg.snap->ps.stats[STAT_HEALTH], + cg.snap->ps.stats[STAT_ARMOR], hcolor ); +} + + +static int propMapTiny[CHARMAX][3]; +static int propMap[CHARMAX][3]; +static int propMapBig[CHARMAX][3]; + +static int propMapB[26][3] = { +{11, 12, 33}, +{49, 12, 31}, +{85, 12, 31}, +{120, 12, 30}, +{156, 12, 21}, +{183, 12, 21}, +{207, 12, 32}, + +{13, 55, 30}, +{49, 55, 13}, +{66, 55, 29}, +{101, 55, 31}, +{135, 55, 21}, +{158, 55, 40}, +{204, 55, 32}, + +{12, 97, 31}, +{48, 97, 31}, +{82, 97, 30}, +{118, 97, 30}, +{153, 97, 30}, +{185, 97, 25}, +{213, 97, 30}, + +{11, 139, 32}, +{42, 139, 51}, +{93, 139, 32}, +{126, 139, 31}, +{158, 139, 25}, +}; + +#define PROPB_GAP_WIDTH 4 +#define PROPB_SPACE_WIDTH 12 +#define PROPB_HEIGHT 36 + +#define TEXTSHEET_SIZE 512 //TiM +#define ORIG_SCALE TEXTSHEET_SIZE / 256 + +/* +================= +UI_DrawBannerString +================= +*/ +static void UI_DrawBannerString2( int x, int y, const char* str, vec4_t color ) +{ + const char* s; + char ch; + float ax; + float ay; + float aw; + float ah; + float frow; + float fcol; + float fwidth; + float fheight; + + // draw the colored text + trap_R_SetColor( color ); + +// ax = x * cgs.screenXScale + cgs.screenXBias; + ax = x * cgs.screenXScale; + ay = y * cgs.screenYScale; + + s = str; + while ( *s ) + { + ch = *s & 255; + if ( ch == ' ' ) { + ax += ((float)PROPB_SPACE_WIDTH + (float)PROPB_GAP_WIDTH)* cgs.screenXScale; + } + else if ( ch >= 'A' && ch <= 'Z' ) { + ch -= 'A'; + fcol = (float)propMapB[ch][0] / 256; + frow = (float)propMapB[ch][1] / 256; + fwidth = (float)propMapB[ch][2] / 256; + fheight = (float)PROPB_HEIGHT / 256; + aw = (float)(propMapB[ch][2] * cgs.screenXScale); + ah = (float)(PROPB_HEIGHT * cgs.screenYScale); + trap_R_DrawStretchPic( ax, ay, aw, ah, fcol, frow, fcol+fwidth, frow+fheight, cgs.media.charsetPropB ); + ax += (aw + (float)PROPB_GAP_WIDTH * cgs.screenXScale); + } + s++; + } + + trap_R_SetColor( NULL ); +} + +void UI_DrawBannerString( int x, int y, const char* str, int style, vec4_t color ) { + const char * s; + int ch; + int width; + vec4_t drawcolor; + + // find the width of the drawn text + s = str; + width = 0; + while ( *s ) { + ch = *s; + if ( ch == ' ' ) { + width += PROPB_SPACE_WIDTH; + } + else if ( ch >= 'A' && ch <= 'Z' ) { + width += propMapB[ch - 'A'][2] + PROPB_GAP_WIDTH; + } + s++; + } + width -= PROPB_GAP_WIDTH; + + switch( style & UI_FORMATMASK ) { + case UI_CENTER: + x -= width / 2; + break; + + case UI_RIGHT: + x -= width; + break; + + case UI_LEFT: + default: + break; + } + + if ( style & UI_DROPSHADOW ) { + drawcolor[0] = drawcolor[1] = drawcolor[2] = 0; + drawcolor[3] = color[3]; + UI_DrawBannerString2( x+2, y+2, str, drawcolor ); + } + + UI_DrawBannerString2( x, y, str, color ); +} + +#define MAX_STRINGWIDTH 2048 + +/* +================= +UI_ProportionalSizeScale +================= +*/ +int UI_ProportionalStringWidth( const char* str,int style ) +{ + const char * s; + int ch; + int charWidth; + int width; + char holdStr[2048]; + + Q_strncpyz( holdStr, str, MAX_STRINGWIDTH ); + + Q_CleanStr(holdStr); + + if (style & UI_TINYFONT) + { + s = holdStr; + width = 0; + while ( *s ) + { + ch = *s & 255; + charWidth = propMapTiny[ch][2]; + if ( charWidth != -1 ) + { + width += charWidth; + width += PROP_GAP_TINY_WIDTH; + } + s++; + } + + width -= PROP_GAP_TINY_WIDTH; + } + else if (style & UI_BIGFONT) + { + s = holdStr; + width = 0; + while ( *s ) + { + ch = *s & 255; + charWidth = propMapBig[ch][2]; + if ( charWidth != -1 ) + { + width += charWidth; + width += PROP_GAP_BIG_WIDTH; + } + s++; + } + + width -= PROP_GAP_BIG_WIDTH; + } + else + { + s = holdStr; + width = 0; + while ( *s ) + { + ch = *s & 255; + charWidth = propMap[ch][2]; + if ( charWidth != -1 ) + { + width += charWidth; + width += PROP_GAP_WIDTH; + } + s++; + } + + width -= PROP_GAP_WIDTH; + } + + return width; +} + +static int specialTinyPropChars[CHARMAX][2] = { +{0, 0}, +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 10 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 20 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 30 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 40 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 50 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 60 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 70 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 80 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 90 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 100 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 110 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 120 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 130 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 140 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 150 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{2,-3},{0, 0}, // 160 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 170 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 180 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 190 +{0,-1},{2,-3},{2,-3},{2,-3},{2,-3},{2,-3},{2,-3},{0, 0},{2, 0},{2,-3}, // 200 +{2,-3},{2,-3},{2,-3},{2,-3},{2,-3},{2,-3},{2,-3},{0,-1},{2,-3},{2,-3}, // 210 +{2,-3},{3,-3},{2,-3},{2,-3},{0, 0},{0,-1},{2,-3},{2,-3},{2,-3},{2,-3}, // 220 +{2,-3},{0,-1},{0,-1},{2,-3},{2,-3},{2,-3},{2,-3},{2,-3},{2,-3},{0, 0}, // 230 +{2, 0},{2,-3},{2,-3},{2,-3},{2,-3},{2,-3},{2,-3},{2,-3},{2,-3},{0, 0}, // 240 +{2,-3},{2,-3},{2,-3},{2,-3},{2,-3},{2,-3},{0, 0},{0,-1},{2,-3},{2,-3}, // 250 +{2,-3},{2,-3},{2,-3},{0,-1},{2,-3} // 255 +}; + + +static int specialPropChars[CHARMAX][2] = { +{0, 0}, +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 10 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 20 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 30 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 40 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 50 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 60 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 70 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 80 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 90 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 100 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 110 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 120 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 130 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 140 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 150 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 160 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 170 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 180 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 190 +{2,-2},{2,-2},{2,-2},{2,-2},{2,-2},{2,-2},{2,-2},{0, 0},{1, 1},{2,-2}, // 200 +{2,-2},{2,-2},{2,-2},{2,-2},{2,-2},{2,-2},{2,-2},{0, 0},{2,-2},{2,-2}, // 210 +{2,-2},{2,-2},{2,-2},{2,-2},{0, 0},{0, 0},{2,-2},{2,-2},{2,-2},{2,-2}, // 220 +{2,-2},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 230 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 240 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 250 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0} // 255 +}; + + +static int specialBigPropChars[CHARMAX][2] = { +{0, 0}, +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 10 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 20 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 30 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 40 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 50 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 60 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 70 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 80 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 90 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 100 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 110 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 120 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 130 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 140 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 150 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 160 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 170 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 180 +{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0},{0, 0}, // 190 +{3,-3},{3,-3},{3,-3},{3,-3},{3,-3},{3,-3},{3,-3},{0, 0},{3, 1},{3,-3}, // 200 +{3,-3},{3,-3},{3,-3},{3,-3},{3,-3},{3,-3},{3,-3},{0, 0},{3,-3},{3,-3}, // 210 +{3,-3},{3,-3},{3,-3},{3,-3},{0, 0},{0, 0},{3,-3},{3,-3},{3,-3},{3,-3}, // 220 +{3,-3},{0, 0},{0, 0},{3,-3},{3,-3},{3,-3},{3,-3},{3,-3},{3,-3},{0, 0}, // 230 +{3, 1},{3,-3},{3,-3},{3,-3},{3,-3},{3,-3},{3,-3},{3,-3},{3,-3},{0, 0}, // 240 +{3,-3},{3,-3},{3,-3},{3,-3},{3,-3},{3,-3},{0, 0},{0, 0},{3,-3},{3,-3}, // 250 +{3,-3},{3,-3},{3,-3},{0, 0},{3,-3} // 255 +}; + +/* +================= +UI_AdjustForWideScreen +================= +*/ +static void UI_AdjustForWidescreen( float *x, float *w ) +{ + if ( CG_IsWidescreen() ) + *w *= cgs.widescreen.ratio; +} + +/* +================= +UI_DrawProportionalString2 +================= +*/ +static void UI_DrawProportionalString2( int x, int y, const char* str, vec4_t color, float sizeScale,int style, qhandle_t charset,qboolean forceColor ) +{ + const char* s; + unsigned char ch; + float ax; + float ay,holdY; + float aw = 0; // stop compiler complaining about uninitialised vars + float ah; + float frow; + float fcol; + float fwidth; + float fheight; + vec4_t givenColor; + int colorI; + int special; + + givenColor[0] = color[0]; + givenColor[1] = color[1]; + givenColor[2] = color[2]; + givenColor[3] = color[3]; + + // draw the colored text + trap_R_SetColor( color ); + +// ax = x * cgs.screenXScale + cgs.screenXBias; + ax = x * cgs.screenXScale; + ay = y * cgs.screenYScale; + holdY = ay; + + if ( CG_IsWidescreen() ) + { + ax *= cgs.widescreen.ratio; + + if ( cgs.widescreen.state == WIDESCREEN_CENTER ) + ax += cgs.widescreen.bias; + else if ( cgs.widescreen.state == WIDESCREEN_RIGHT ) + ax += (cgs.widescreen.bias*2); + } + + if (style & UI_TINYFONT) + { + s = str; + while ( *s ) + { + // Is there a color character + if ( Q_IsColorString( s ) ) + { + if (!forceColor) + { + colorI = ColorIndex( *(s+1) ); + trap_R_SetColor( g_color_table[colorI] ); + } + s += 2; + continue; + } + + //CG_Printf("s = %s, ch = %s", s, ch); + + ch = *s & 255; + if ( ch == ' ' ) + { + aw = (float)PROP_SPACE_TINY_WIDTH; + } + else if ( propMapTiny[ch][2] != -1 ) + { + // Because some foreign characters were a little different + special = specialTinyPropChars[ch][0]; + ay = holdY + (specialTinyPropChars[ch][1] * cgs.screenYScale); + + fcol = (float ) propMapTiny[ch][0] / 256; + frow = (float)propMapTiny[ch][1] / 256; + fwidth = (float)propMapTiny[ch][2] / 256; + fheight = (float)(PROP_TINY_HEIGHT + special) / 256; + aw = (float)propMapTiny[ch][2] * cgs.screenXScale * sizeScale; + ah = (float)(PROP_TINY_HEIGHT + special) * cgs.screenYScale * sizeScale; + + //TiM - adjust for widescreen + UI_AdjustForWidescreen( &ax, &aw ); + + trap_R_DrawStretchPic( ax, ay, aw, ah, fcol, frow, fcol + fwidth, frow + fheight, charset ); + + } + + ax += (aw + (float)PROP_GAP_TINY_WIDTH * cgs.screenXScale * sizeScale); + + if ( CG_IsWidescreen() ) + ax -= ((float)PROP_GAP_TINY_WIDTH * cgs.screenXScale * sizeScale)*(1.0f-cgs.widescreen.ratio); + + s++; + } + } + else if (style & UI_BIGFONT) + { + s = str; + while ( *s ) + { + // Is there a color character + if ( Q_IsColorString( s ) ) + { + if (!forceColor) + { + colorI = ColorIndex( *(s+1) ); + trap_R_SetColor( g_color_table[colorI] ); + } + s += 2; + continue; + } + + ch = *s & 255; + if ( ch == ' ' ) + { + aw = (float)PROP_SPACE_BIG_WIDTH* cgs.screenXScale * sizeScale; + } + else if ( propMap[ch][2] != -1 ) + { + // Because some foreign characters were a little different + special = specialBigPropChars[ch][0]; + ay = holdY + (specialBigPropChars[ch][1] * cgs.screenYScale); + + fcol = (float ) propMapBig[ch][0] / 256; //256.0f + frow = (float)propMapBig[ch][1] / 256; + fwidth = (float)propMapBig[ch][2] / 256; + fheight = (float)(PROP_BIG_HEIGHT+ special) / 256; + aw = (float)propMapBig[ch][2] * cgs.screenXScale * sizeScale; + ah = (float)(PROP_BIG_HEIGHT+ special) * cgs.screenYScale * sizeScale; + + //TiM - adjust for widescreen + UI_AdjustForWidescreen( &ax, &aw ); + + trap_R_DrawStretchPic( ax, ay, aw, ah, fcol, frow, fcol + fwidth, frow + fheight, + charset ); + + } + else + { + aw = 0; + } + + ax += (aw + (float)PROP_GAP_BIG_WIDTH * cgs.screenXScale * sizeScale); + + if ( CG_IsWidescreen() ) + ax -= ((float)PROP_GAP_BIG_WIDTH * cgs.screenXScale * sizeScale)*(1.0f-cgs.widescreen.ratio); + + + s++; + } + } + else + { + s = str; + while ( *s ) + { + // Is there a color character + if ( Q_IsColorString( s ) ) + { + if (!forceColor) + { + colorI = ColorIndex( *(s+1) ); + trap_R_SetColor( g_color_table[colorI] ); + } + s += 2; + continue; + } + + ch = *s & 255; + if ( ch == ' ' ) + { + aw = (float)PROP_SPACE_WIDTH * cgs.screenXScale * sizeScale; + } + else if ( propMap[ch][2] != -1 ) + { + // Because some foreign characters were a little different + special = specialPropChars[ch][0]; + ay = holdY + (specialPropChars[ch][1] * cgs.screenYScale); + + fcol = (float)propMap[ch][0] / 256; + frow = (float)propMap[ch][1] / 256; + fwidth = (float)propMap[ch][2] / 256; + fheight = (float)(PROP_HEIGHT+ special) / 256; + aw = (float)(propMap[ch][2] * cgs.screenXScale * sizeScale); + ah = (float)((PROP_HEIGHT+ special) * cgs.screenYScale * sizeScale); + + //TiM - adjust for widescreen + UI_AdjustForWidescreen( &ax, &aw ); + + trap_R_DrawStretchPic( ax, ay, aw, ah, fcol, frow, fcol+fwidth, frow+fheight, charset ); + } + else + { + aw = 0; + } + + ax += (aw + (float)PROP_GAP_WIDTH * cgs.screenXScale * sizeScale); + + if ( CG_IsWidescreen() ) + ax -= ((float)PROP_GAP_WIDTH * cgs.screenXScale * sizeScale)*(1.0f-cgs.widescreen.ratio); + + s++; + } + } + trap_R_SetColor( NULL ); +} + +/* +================= +UI_ProportionalSizeScale +================= +*/ +float UI_ProportionalSizeScale( int style ) +{ + if( style & UI_SMALLFONT ) + { + return 1; + } + + return 1.00; +} + + +/* +================= +UI_DrawProportionalString +================= +*/ +void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ) +{ + vec4_t drawcolor; + int width; + float sizeScale; + qhandle_t charset; + + sizeScale = UI_ProportionalSizeScale( style ); + + switch( style & UI_FORMATMASK ) + { + case UI_CENTER: + width = UI_ProportionalStringWidth( str, style ) * sizeScale; + x -= width / 2; + break; + + case UI_RIGHT: + width = UI_ProportionalStringWidth( str,style ) * sizeScale; + x -= width; + break; + + case UI_LEFT: + default: + break; + } + + //TiM - vertical align + if ( style & UI_BOTTOM ) + { + if (style & UI_TINYFONT) + y -= (PROP_TINY_HEIGHT+3); + else if (style & UI_BIGFONT) + y -= (PROP_BIG_HEIGHT+3); + else + y -= (PROP_HEIGHT+3); + } + + if (style & UI_TINYFONT) + { + charset = cgs.media.charsetPropTiny; + } + else if (style & UI_BIGFONT) + { + charset = cgs.media.charsetPropBig; + sizeScale = 1; + } + else + { + charset = cgs.media.charsetProp; + sizeScale = 1; + } + + if ( style & UI_DROPSHADOW ) + { + drawcolor[0] = drawcolor[1] = drawcolor[2] = 0; + drawcolor[3] = color[3]; + + UI_DrawProportionalString2( x+2, y+2, str, drawcolor, sizeScale,style, charset,qtrue ); + } + + if ( style & UI_INVERSE ) + { + drawcolor[0] = color[0] * 0.8; + drawcolor[1] = color[1] * 0.8; + drawcolor[2] = color[2] * 0.8; + drawcolor[3] = color[3]; + UI_DrawProportionalString2( x, y, str, drawcolor, sizeScale,style, charset,qtrue ); + return; + } + + if ( style & UI_PULSE ) + { + drawcolor[0] = color[0] * 0.8; + drawcolor[1] = color[1] * 0.8; + drawcolor[2] = color[2] * 0.8; + drawcolor[3] = color[3]; + UI_DrawProportionalString2( x, y, str, color, sizeScale,style, charset,qtrue ); + + drawcolor[0] = color[0]; + drawcolor[1] = color[1]; + drawcolor[2] = color[2]; + drawcolor[3] = 0.5 + 0.5 * sin( cg.time / PULSE_DIVISOR ); + UI_DrawProportionalString2( x, y, str, drawcolor, sizeScale,style,charset,qtrue ); + return; + } + + if ( style & UI_FORCECOLOR ) + { + UI_DrawProportionalString2( x, y, str, color, sizeScale,style, charset,qtrue); + return; + } + + UI_DrawProportionalString2( x, y, str, color, sizeScale,style, charset,qfalse); +} + +/* +============== +CG_DrawNumField + +Take x,y positions as if 640 x 480 and scales them to the proper resolution + +============== +*/ +void CG_DrawNumField (int x, int y, int width, int value,int charWidth,int charHeight,int style) +{ + char num[16], *ptr; + int l; + int frame; + int xWidth; + + if (width < 1) { + return; + } + + // draw number string + if (width > 5) { + width = 5; + } + + switch ( width ) { + case 1: + value = value > 9 ? 9 : value; + value = value < 0 ? 0 : value; + break; + case 2: + value = value > 99 ? 99 : value; + value = value < -9 ? -9 : value; + break; + case 3: + value = value > 999 ? 999 : value; + value = value < -99 ? -99 : value; + break; + case 4: + value = value > 9999 ? 9999 : value; + value = value < -999 ? -999 : value; + break; + } + + Com_sprintf (num, sizeof(num), "%i", value); + l = strlen(num); + if (l > width) + l = width; + + if (style & NUM_FONT_SMALL) + { + xWidth = 4 + 2; + } + else + { + xWidth = (charWidth/2) + 3;//(charWidth/6); + } + + x += 2 + (xWidth)*(width - l); + + ptr = num; + while (*ptr && l) + { + if (*ptr == '-') + frame = STAT_MINUS; + else + frame = *ptr -'0'; + + if (style & NUM_FONT_SMALL) + { + CG_DrawPic( x,y, charWidth, charHeight, cgs.media.smallnumberShaders[frame] ); + } + else + { + CG_DrawPic( x,y, charWidth, charHeight, cgs.media.numberShaders[frame] ); + } + + x += (xWidth); + ptr++; + l--; + } + +} + +/* +================ +CG_PrintInterfaceGraphics +================ +*/ +void CG_PrintInterfaceGraphics(int min,int max) +{ + int i; + vec4_t drawcolor; + + // Printing graphics + for (i=min;ips.persistant[PERS_TEAM] == TEAM_RED ) + { + drawcolor[0] = 1; + } + else if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) + { + drawcolor[2] = 1; + } + + if (interface_graphics[i].type == SG_GRAPHIC) + { + trap_R_SetColor(drawcolor); + + CG_DrawPic( interface_graphics[i].x, + interface_graphics[i].y, + interface_graphics[i].width, + interface_graphics[i].height, + interface_graphics[i].graphic); + } + else if (interface_graphics[i].type == SG_NUMBER) + { + if (interface_graphics[i].style & UI_PULSE) + { + drawcolor[3] = 0.5+0.5*sin(cg.time/PULSE_DIVISOR); + } + trap_R_SetColor( drawcolor); + + CG_DrawNumField ( + interface_graphics[i].x, + interface_graphics[i].y, 3, + interface_graphics[i].max, + interface_graphics[i].width, + interface_graphics[i].height,interface_graphics[i].style); + } + } + + trap_R_SetColor(NULL); +} + +/* +================= +CG_ParseFontParms +================= +*/ +static char *CG_ParseFontParms(char *buffer,int propArray[CHARMAX][3]) +{ + char *token; + int i,i2; + + while ( buffer ) + { + token = COM_ParseExt( &buffer, qtrue ); + + // Start with open braket + if ( !Q_stricmp( token, "{" ) ) + { + for (i=0;i FONT_BUFF_LENGTH) + { + trap_Print( va( S_COLOR_RED "CG_LoadFonts : FONTS.DAT file bigger than %d!\n",FONT_BUFF_LENGTH)); + return; + } + + // initialise the data area + memset(buffer, 0, sizeof(buffer)); + + trap_FS_Read( buffer, len, f ); + + trap_FS_FCloseFile( f ); + + COM_BeginParseSession(); + + holdBuf = (char *) buffer; + holdBuf = CG_ParseFontParms( holdBuf,propMapTiny); + holdBuf = CG_ParseFontParms( holdBuf,propMap); + holdBuf = CG_ParseFontParms( holdBuf,propMapBig); + +} + +/* +================= +CG_ParseRankData + +Takes '{ x, y, w, h, file_route } and parses it. +================= +*/ + +#define MAX_RANK_WIDTH 67 +#define MAX_RANK_HEIGHT 15 + +qboolean CG_ParseRankData( char **data, rankMenuData_t* rankMenuData ) { + const char* token; + + token = COM_ParseExt( data, qtrue ); + if ( token[0] == 0 ) { + Com_Printf( S_COLOR_RED "Unexpected EOF! O_o\n" ); + return qtrue; + } + + if ( !Q_stricmp( token, "{" ) ) { + //parse w value + token = COM_ParseExt( data, qtrue ); + if ( token[0] == 0 ) { + Com_Printf( S_COLOR_RED "Unexpected EOF! O_o\n" ); + return qtrue; + } + rankMenuData->w = atoi( token ); + + if ( rankMenuData->w > MAX_RANK_WIDTH ) { + rankMenuData->w = MAX_RANK_WIDTH; + } + + //parse h value + token = COM_ParseExt( data, qtrue ); + if ( token[0] == 0 ) { + Com_Printf( S_COLOR_RED "Unexpected EOF! O_o\n" ); + return qtrue; + } + rankMenuData->h = atoi( token ); + + if ( rankMenuData->h > MAX_RANK_HEIGHT ) { + rankMenuData->h = MAX_RANK_HEIGHT; + } + + //parse s1 value + token = COM_ParseExt( data, qtrue ); + if ( token[0] == 0 ) { + Com_Printf( S_COLOR_RED "Unexpected EOF! O_o\n" ); + return qtrue; + } + rankMenuData->s1 = atoi( token ); + + if ( !rankMenuData->s1 ) { + Com_Printf( S_COLOR_RED "Invalid s1 value in rankset\n"); + return qtrue; + } + + //parse t1 value + token = COM_ParseExt( data, qtrue ); + if ( token[0] == 0 ) { + Com_Printf( S_COLOR_RED "Unexpected EOF! O_o\n" ); + return qtrue; + } + rankMenuData->t1 = atoi( token ); + + if ( !rankMenuData->t1 ) { + Com_Printf( S_COLOR_RED "Invalid t1 value in rankset\n"); + return qtrue; + } + + //parse s2 value + token = COM_ParseExt( data, qtrue ); + if ( token[0] == 0 ) { + Com_Printf( S_COLOR_RED "Unexpected EOF! O_o\n" ); + return qtrue; + } + rankMenuData->s2 = atoi( token ); + + if ( !rankMenuData->s2 ) { + Com_Printf( S_COLOR_RED "Invalid s2 value in rankset\n"); + return qtrue; + } + + //parse t2 value + token = COM_ParseExt( data, qtrue ); + if ( token[0] == 0 ) { + Com_Printf( S_COLOR_RED "Unexpected EOF! O_o\n" ); + return qtrue; + } + rankMenuData->t2 = atoi( token ); + + if ( !rankMenuData->t2 ) { + Com_Printf( S_COLOR_RED "Invalid t2 value in rankset\n"); + return qtrue; + } + + //parse gWidth value + token = COM_ParseExt( data, qtrue ); + if ( token[0] == 0 ) { + Com_Printf( S_COLOR_RED "Unexpected EOF! O_o\n" ); + return qtrue; + } + rankMenuData->gWidth = atoi( token ); + + if ( !rankMenuData->gWidth ) { + Com_Printf( S_COLOR_RED "Invalid graphic width value in rankset\n"); + return qtrue; + } + + //parse gHeight value + token = COM_ParseExt( data, qtrue ); + if ( token[0] == 0 ) { + Com_Printf( S_COLOR_RED "Unexpected EOF! O_o\n" ); + return qtrue; + } + rankMenuData->gHeight = atoi( token ); + + if ( !rankMenuData->gHeight ) { + Com_Printf( S_COLOR_RED "Invalid graphic height value in rankset\n"); + return qtrue; + } + + //parse graphics handle + token = COM_ParseExt( data, qtrue ); + if ( token[0] == 0 ) { + Com_Printf( S_COLOR_RED "Unexpected EOF! O_o\n" ); + return qtrue; + } + rankMenuData->graphic = trap_R_RegisterShaderNoMip( token ); + + if ( !rankMenuData->graphic ) { + Com_Printf( S_COLOR_RED "Unable to find shader in rankset: %s\n", token ); + return qtrue; + } + + //error checking - we should see a '}' now :P + token = COM_ParseExt( data, qtrue ); + if ( token[0] == 0 ) { + Com_Printf( S_COLOR_RED "Unexpected EOF! O_o\n" ); + return qtrue; + } + + //all good :) return the function good + if ( !Q_stricmp( token, "}" ) ) { + return qfalse; + } + else { + return qtrue; + } + } + else { + Com_Printf( S_COLOR_RED, "Could not find { in current rankset file.\n" ); + return qtrue; + } +} + +/* +================= +CG_LoadRanks +By TiM +================= +*/ +qboolean CG_LoadRanks( void ) { + fileHandle_t file; + int file_len; + char charText[32000]; + char *textPtr, *prevValue; + char fileName[MAX_QPATH]; + int i; + int rankCount=0; + char *token; + + qboolean DefaultRankLoaded = qfalse; + + if ( cgs.rankSet ) { + Com_sprintf( fileName, sizeof( fileName ), "ext_data/ranksets/%s.ranks", cgs.rankSet ); + } + else { + return qfalse; + } + + file_len = trap_FS_FOpenFile( fileName, &file, FS_READ ); + + if ( file_len <= 0 ) { + return qfalse; + } + + if ( file_len >= sizeof( charText ) - 1 ) { + Com_Printf( S_COLOR_RED "Size of rankset file %s is too large.\n", fileName ); + return qfalse; + } + + memset( &charText, 0, sizeof( charText ) ); + memset( &cgs.defaultRankData, 0, sizeof( cgs.defaultRankData ) ); + memset( &cgs.ranksData, 0, sizeof( cgs.ranksData ) ); + + trap_FS_Read( charText, file_len, file ); + + charText[file_len] = 0; + + trap_FS_FCloseFile( file ); + + COM_BeginParseSession(); + + textPtr = charText; + + token = COM_Parse( &textPtr ); + + if ( !token[0] ) { + Com_Printf( S_COLOR_RED "No data found in rankset: %s\n", fileName ); + return qfalse; + } + + if ( Q_stricmp( token, "{" ) ) { + Com_Printf( S_COLOR_RED "Missing starting { in rankset file: %s\n", fileName); + return qfalse; + } + + //Parse the first one only... the first one should be the DEFAULT RANK!!! + //DEFAULT I SAY! IT'S SPECIAL! + //WE NEED A DEFAULT FOR ERRORS! NO DEFAULT NO RANK FOR YOU! + while ( 1 ) { + prevValue = textPtr; + + token = COM_Parse( &textPtr ); + if ( !token[0] ) { + break; + } + + if ( !Q_stricmpn( token, "MenuTextureDef", 14 ) ) { + if ( CG_ParseRankData( &textPtr, &cgs.defaultRankData.rankMenuData ) ) { + continue; + } + + DefaultRankLoaded = qtrue; + continue; + } + else if ( !Q_stricmpn( token, "BoltModel", 9 ) ) { + if ( COM_ParseString( &textPtr, &token ) ) { + continue; + } + + Q_strncpyz( cgs.defaultRankData.rankModelData.boltModelPath, token, + sizeof( cgs.defaultRankData.rankModelData.boltModelPath ) ); + + /*if ( !cgs.defaultRankData.boltModel ) { + Com_Printf(S_COLOR_RED "Unable to load model file: %s\n", token ); + }*/ + + continue; + + } + else if ( !Q_stricmpn( token, "boltShader", 8 ) ) { + if ( COM_ParseString( &textPtr, &token ) ) { + continue; + } + + Q_strncpyz( cgs.defaultRankData.rankModelData.boltShaderPath, token, + sizeof( cgs.defaultRankData.rankModelData.boltShaderPath ) ); + + /*cgs.defaultRankData.boltShader = trap_R_RegisterSkin( token ); + if ( !cgs.defaultRankData.boltShader ) { + Com_Printf(S_COLOR_RED "Unable to load skin file: %s\n", token ); + }*/ + + continue; + } + else if ( !Q_stricmpn( token, "AdmiralRank", 11 ) ) { + if ( COM_ParseInt( &textPtr, &i ) ) { + continue; + } + + cgs.defaultRankData.rankModelData.admiralRank = i; + continue; + } + else if ( !Q_stricmp( token, "}" ) ) { + break; + } + + } + + //if we succeeded in loading a default value (^_^!), then begin parsing the main ranks + if ( !DefaultRankLoaded ) { + Com_Printf( S_COLOR_RED "No default rank located in %s\n", fileName ); + return qfalse; + } + else { + //Parse.Rank.Data? :) *sigh* if only rofl + while (1) { + prevValue = textPtr; + token = COM_Parse( &textPtr ); + if ( !token[0] ) { + break; + } + + if ( rankCount >= MAX_RANKS ) { + break; + } + + if ( !Q_stricmpn( token, "{", 1 ) ) { + + while ( 1 ) { + token = COM_Parse( &textPtr ); + if ( !token[0] ) { + break; + } + + if ( !Q_stricmpn( token, "ConsoleName", 11 ) ) { + if ( COM_ParseString( &textPtr, &token ) ) { + Com_Printf( S_COLOR_RED "Could not parse console rank name for rank number: %i\n", rankCount ); + return qfalse; + } + + Q_strncpyz( cgs.ranksData[rankCount].consoleName, token, sizeof( cgs.ranksData[rankCount].consoleName ) ); + continue; + } + + if ( !Q_stricmpn( token, "FormalName", 10 ) ) { + if ( COM_ParseString( &textPtr, &token ) ) { + Com_Printf( S_COLOR_RED "Could not parse formal rank name for rank number: %i\n", rankCount ); + return qfalse; + } + + Q_strncpyz( cgs.ranksData[rankCount].formalName, token, sizeof( cgs.ranksData[rankCount].formalName ) ); + continue; + } + + if ( !Q_stricmpn( token, "MenuTextureRed", 14 ) ) { + if ( CG_ParseRankData( &textPtr, &cgs.ranksData[rankCount].rankMenuData[CLR_RED] ) ) { + Com_Printf( S_COLOR_RED "Could not parse red rank color for rank number: %i\n", rankCount ); + return qfalse; + } + + continue; + } + else if ( !Q_stricmpn( token, "MenuTextureTeal", 15 ) ) { + if ( CG_ParseRankData( &textPtr, &cgs.ranksData[rankCount].rankMenuData[CLR_TEAL] ) ) { + Com_Printf( S_COLOR_RED "Could not parse teal rank color for rank number: %i\n", rankCount ); + return qfalse; + } + + continue; + } + else if ( !Q_stricmpn( token, "MenuTextureGold", 15 ) ) { + if ( CG_ParseRankData( &textPtr, &cgs.ranksData[rankCount].rankMenuData[CLR_GOLD] ) ) { + Com_Printf( S_COLOR_RED "Could not parse teal rank color for rank number: %i\n", rankCount ); + return qfalse; + } + + continue; + } + else if ( !Q_stricmpn( token, "MenuTextureGreen", 16 ) ) { + if ( CG_ParseRankData( &textPtr, &cgs.ranksData[rankCount].rankMenuData[CLR_GREEN] ) ) { + Com_Printf( S_COLOR_RED "Could not parse green rank color for rank number: %i\n", rankCount ); + return qfalse; + } + + continue; + } + else if ( !Q_stricmpn( token, "BoltModel", 9 ) ) { + if ( COM_ParseString( &textPtr, &token ) ) { + continue; + } + + //cgs.ranksData[rankCount].boltModel = trap_R_RegisterModel( token ); + + Q_strncpyz( cgs.ranksData[rankCount].rankModelData.boltModelPath, + token, + sizeof( cgs.ranksData[rankCount].rankModelData.boltModelPath) ); + + /*if ( !cgs.ranksData[rankCount].boltModel ) { + Com_Printf( S_COLOR_RED, "Could not load bolt model: %s\n", token ); + }*/ + continue; + } + else if ( !Q_stricmpn( token, "boltShader", 8 ) ) { + if ( COM_ParseString( &textPtr, &token ) ) { + continue; + } + + Q_strncpyz( cgs.ranksData[rankCount].rankModelData.boltShaderPath, + token, + sizeof( cgs.ranksData[rankCount].rankModelData.boltShaderPath) ); + + + /*if ( !cgs.ranksData[rankCount].boltShader ) { + Com_Printf( S_COLOR_RED, "Could not load bolt skin: %s\n", token ); + }*/ + continue; + } + else if ( !Q_stricmpn( token, "AdmiralRank", 11 ) ) { + if ( COM_ParseInt( &textPtr, &i ) ) { + continue; + } + + cgs.ranksData[rankCount].rankModelData.admiralRank = i; + + continue; + } + else if ( !Q_stricmpn( token, "}", 1 ) ) { + break; + } + } + rankCount++; + } + } + } + return qtrue; +} + +/* +================= +CG_LoadCrosshairs +By TiM + +Parsing script to load +and display multiple +crosshairs. +================= +*/ +qboolean CG_LoadCrosshairs(void) { + fileHandle_t f; + int file_len; + char charText[20000]; + char *token, *textPtr; + char *fileName = "ext_data/crosshairs.dat"; + int cHairCount = 0; + //int i; + + //load file and get file length + file_len = trap_FS_FOpenFile( fileName, &f, FS_READ ); + + memset( &charText, 0, sizeof( charText ) ); + memset( &cgs.crosshairsData, 0, sizeof( &cgs.crosshairsData ) ); + + //check to see if we got anything + if ( file_len <= 0 ) { + Com_Printf( S_COLOR_RED "Could not find file: %s\n", fileName ); + return qfalse; + } + + //check to see if we got too much + if ( file_len > sizeof( charText) - 1 ) { + Com_Printf( S_COLOR_RED "File %s waaaaay too big.\n", fileName ); + return qfalse; + } + + //read the data into the array + trap_FS_Read( charText, file_len, f ); + + //EOF? + charText[file_len] = 0; + + trap_FS_FCloseFile( f ); + + COM_BeginParseSession(); + + textPtr = charText; + + token = COM_Parse( &textPtr ); + + //wtf? Nothing here?? + if ( !token[0] ) { + Com_Printf( S_COLOR_RED "File: %s terminated rather unexpectantly\n", fileName ); + return qfalse; + } + + if ( Q_stricmp( token, "{" ) ) { + Com_Printf( S_COLOR_RED "File: %s had no starting {\n", fileName ); + return qfalse; + } + + //now for the natural hard-core loop parsing bit + while ( 1 ) { + + if ( cHairCount >= MAX_CROSSHAIRS ) { + break; + } + + if ( !Q_stricmpn( token, "{", 1 ) ) { + //Make color default to white + VectorCopy( colorTable[CT_WHITE], cgs.crosshairsData[cHairCount].color ); + cgs.crosshairsData[cHairCount].color[3] = 1.0f; + + while ( 1 ) + { + token = COM_Parse( &textPtr ); + if (!token[0]) { + break; + } + + if ( !Q_stricmpn( token, "noDraw", 6 ) ) { + cgs.crosshairsData[cHairCount].noDraw = qtrue; + break; + } + else if ( !Q_stricmpn( token, "CrosshairColor", 14 ) ) { + if ( COM_ParseVec4( &textPtr, cgs.crosshairsData[cHairCount].color ) ) { + Com_Printf( S_COLOR_RED "Invalid Color Value in %s\n", fileName ); + return qfalse; + } + + VectorScale( cgs.crosshairsData[cHairCount].color, 1.0f/255.0f, cgs.crosshairsData[cHairCount].color ); + cgs.crosshairsData[cHairCount].color[3] *= 1.0f/255.0f; + + continue; + } + else if ( !Q_stricmpn( token, "BitmapCoords", 12 ) ) { + if ( COM_ParseString( &textPtr, &token ) ) { + Com_Printf( S_COLOR_RED "No bitmap co-ords found in %s\n", fileName ); + return qfalse; + } + + if ( Q_stricmpn( token, "{", 1 ) ) { + Com_Printf( S_COLOR_RED "No { found in bitmap co-ords in %s\n", fileName ); + return qfalse; + } + + //parse s1 value + if ( COM_ParseString( &textPtr, &token ) ) { + Com_Printf( S_COLOR_RED "Bit co-ord value ended prematurely: %s\n", fileName ); + return qfalse; + } + + cgs.crosshairsData[cHairCount].s1 = atoi( token ); + + //parse t1 value + if ( COM_ParseString( &textPtr, &token ) ) { + Com_Printf( S_COLOR_RED "Bit co-ord value ended prematurely: %s\n", fileName ); + return qfalse; + } + + cgs.crosshairsData[cHairCount].t1 = atoi( token ); + + //parse s2 value + if ( COM_ParseString( &textPtr, &token ) ) { + Com_Printf( S_COLOR_RED "Bit co-ord value ended prematurely: %s\n", fileName ); + return qfalse; + } + + cgs.crosshairsData[cHairCount].s2 = atoi( token ); + + //parse t2 value + if ( COM_ParseString( &textPtr, &token ) ) { + Com_Printf( S_COLOR_RED "Bit co-ord value ended prematurely: %s\n", fileName ); + return qfalse; + } + + cgs.crosshairsData[cHairCount].t2 = atoi( token ); + + //make sure we have } at the end FFS + if ( COM_ParseString( &textPtr, &token ) ) { + Com_Printf( S_COLOR_RED "Bit co-ord value ended prematurely: %s\n", fileName ); + return qfalse; + } + + //This is needed or else we can't tell between this and the main closing } + if ( Q_stricmpn( token, "}", 1 ) ) { + Com_Printf( S_COLOR_RED "No terminating } in bitmap co-ord: %s\n", fileName ); + return qfalse; + } + } + else if ( !Q_stricmpn ( token, "}", 1 ) ) { + break; + } + } + cHairCount++; + } + + token = COM_Parse( &textPtr ); + if (!token[0]) { + break; + } + } + return qtrue; +} diff --git a/cgame/cg_effects.c b/cgame/cg_effects.c new file mode 100644 index 0000000..255a3c3 --- /dev/null +++ b/cgame/cg_effects.c @@ -0,0 +1,609 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_effects.c -- these functions generate localentities, usually as a result +// of event processing + +#include "cg_local.h" +#include "fx_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; + + 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->shaderTime = cg.time * 0.001f; + + re->reType = RT_SPRITE; + re->data.sprite.rotation = 0; + re->data.sprite.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); + } +} + +/* +===================== +CG_SmokePuff + +Adds a smoke puff or blood trail localEntity. +===================== +*/ +localEntity_t *CG_SmokePuff( const vec3_t p, const vec3_t vel, + float radius, + float r, float g, float b, float a, + float duration, + int startTime, + int leFlags, + qhandle_t hShader ) { + static int seed = 0x92; + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + le->leFlags = leFlags; + le->data.sprite.radius = radius; + + re = &le->refEntity; + re->data.sprite.rotation = Q_random( &seed ) * 360; + re->data.sprite.radius = radius; + //re->shaderTime = startTime / 1000.0f; + re->shaderTime = startTime * 0.001f; + + le->leType = LE_MOVE_SCALE_FADE; + le->startTime = startTime; + le->endTime = startTime + duration; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + le->color[0] = r; + le->color[1] = g; + le->color[2] = b; + le->color[3] = a; + + + le->pos.trType = TR_LINEAR; + le->pos.trTime = startTime; + VectorCopy( vel, le->pos.trDelta ); + VectorCopy( p, le->pos.trBase ); + + VectorCopy( p, re->origin ); + re->customShader = hShader; + + // rage pro can't alpha fade, so use a different shader + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) { + re->customShader = cgs.media.smokePuffRageProShader; + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0xff; + re->shaderRGBA[2] = 0xff; + re->shaderRGBA[3] = 0xff; + } else { + re->shaderRGBA[0] = le->color[0] * 0xff; + re->shaderRGBA[1] = le->color[1] * 0xff; + re->shaderRGBA[2] = le->color[2] * 0xff; + re->shaderRGBA[3] = 0xff; + } + + re->reType = RT_SPRITE; + re->data.sprite.radius = le->data.sprite.radius; + + return le; +} + +/* +================== +CG_SpawnEffect + +Player teleporting in or out + +RPG-X: RedTechie Added refEntity_t *ent_legs, refEntity_t *ent_torso, refEntity_t *ent_head +================== +*/ +void CG_SpawnEffect( vec3_t org, refEntity_t *ent_legs, refEntity_t *ent_torso, refEntity_t *ent_head ) { + localEntity_t *le; + refEntity_t *re; + + FX_Transporter(org); + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->leType = LE_FADE_RGB; + le->startTime = cg.time; + le->endTime = cg.time + 500; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; + + re = &le->refEntity; + //RPG-X: RedTechie - Added for better trans effect - dosnt work right now + //ent_legs->shaderTime = cg.time / 1000.0f; + //ent_head->shaderTime = cg.time / 1000.0f; + //ent_torso->shaderTime = cg.time / 1000.0f; + //ent_legs->customShader = cgs.media.teleportEffectShader; + //trap_R_AddRefEntityToScene( ent_legs ); + //ent_head->customShader = cgs.media.teleportEffectShader; + //trap_R_AddRefEntityToScene( ent_head ); + //ent_torso->customShader = cgs.media.teleportEffectShader; + //trap_R_AddRefEntityToScene( ent_torso ); + + //RPG-X: RedTechie - Playing with transporter crap + //re->shaderTime = cg.time / 1000.0f; + re->shaderTime = cg.time * 0.001f; + re = &le->refEntity; + + re->reType = RT_MODEL; + //re->shaderTime = cg.time / 1000.0f; + re->shaderTime = cg.time * 0.001f; + + re->customShader = cgs.media.teleportEffectShader; + re->hModel = cgs.media.teleportEffectModel; + AxisClear( re->axis ); + + VectorCopy( org, re->origin ); + re->origin[2] -= 24; +} + +void CG_QFlashEvent( vec3_t org ) { + localEntity_t *le; + /*refEntity_t *re; + + le = CG_AllocLocalEntity(); + le->leFlags = LEF_SINE_SCALE; + le->leType = LE_PARTICLE; + + le->startTime = cg.time; + le->endTime = cg.time + 600; + + le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0f; + + le->data.particle.radius = 30; + le->data.particle.dradius = 5; + + re = &le->refEntity; + re->customShader = cgs.media.qFlashSprite; + + VectorCopy( org, re->origin ); + VectorCopy( org, re->oldorigin );*/ + + le = FX_AddParticle( org, vec3_origin, qfalse, 110.0f, 109.0f, + 1.0f, 1.0f, 0, 0, + 290, cgs.media.qFlashSprite, 0 ); + le->leFlags = LEF_SINE_SCALE | LEF_REVERSE_SCALE; + + le->refEntity.renderfx |= RF_DEPTHHACK; +} + + +/* +==================== +CG_MakeExplosion +==================== +*/ +localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, + qhandle_t hModel, qhandle_t shader, + int msec, float scale, qboolean isSprite ) { + float ang; + localEntity_t *ex; + int offset; + vec3_t tmpVec, newOrigin; + + if ( msec <= 0 ) { + CG_Error( "CG_MakeExplosion: msec = %i", msec ); + } + + // skew the time a bit so they aren't all in sync + offset = rand() & 63; + + ex = CG_AllocLocalEntity(); + if ( isSprite ) { + ex->leType = LE_SPRITE_EXPLOSION; + + // randomly rotate sprite orientation + ex->refEntity.data.sprite.rotation = rand() % 360; + VectorScale( dir, 16, tmpVec ); + VectorAdd( tmpVec, origin, newOrigin ); + } else { + ex->leType = LE_EXPLOSION; + VectorCopy( origin, newOrigin ); + + // set axis with random rotate + if ( !dir ) { + AxisClear( ex->refEntity.axis ); + } else { + ang = rand() % 360; + VectorCopy( dir, ex->refEntity.axis[0] ); + RotateAroundDirection( ex->refEntity.axis, ang ); + } + } + + ex->startTime = cg.time - offset; + ex->endTime = ex->startTime + msec; + + // bias the time so all shader effects start correctly + //ex->refEntity.shaderTime = ex->startTime / 1000.0f; + ex->refEntity.shaderTime = ex->startTime * 0.001f; + + ex->refEntity.hModel = hModel; + ex->refEntity.customShader = shader; + + // set origin + VectorCopy( newOrigin, ex->refEntity.origin ); + VectorCopy( newOrigin, ex->refEntity.oldorigin ); + + //Scale the explosion + if (scale != 1) { + ex->refEntity.nonNormalizedAxes = qtrue; + + VectorScale( ex->refEntity.axis[0], scale, ex->refEntity.axis[0] ); + VectorScale( ex->refEntity.axis[1], scale, ex->refEntity.axis[1] ); + VectorScale( ex->refEntity.axis[2], scale, ex->refEntity.axis[2] ); + } + + ex->color[0] = ex->color[1] = ex->color[2] = 1.0; + + return ex; +} + + +localEntity_t *CG_MakeExplosion2( vec3_t origin, vec3_t dir, + qhandle_t hModel, int numFrames, qhandle_t shader, + int msec, qboolean isSprite, float scale, int flags) { + float ang; + localEntity_t *ex; + int offset; + vec3_t tmpVec, newOrigin; + + if ( msec <= 0 ) { + CG_Error( "CG_MakeExplosion: msec = %i", msec ); + } + + // skew the time a bit so they aren't all in sync + offset = rand() & 63; + + ex = CG_AllocLocalEntity(); + if ( isSprite ) { + ex->leType = LE_SPRITE_EXPLOSION; + + // randomly rotate sprite orientation + ex->refEntity.data.sprite.rotation = rand() % 360; + VectorScale( dir, 16, tmpVec ); + VectorAdd( tmpVec, origin, newOrigin ); + } else { + ex->leType = LE_EXPLOSION; + VectorCopy( origin, newOrigin ); + + // set axis with random rotate + if ( !dir ) { + AxisClear( ex->refEntity.axis ); + } else { + ang = rand() % 360; + VectorCopy( dir, ex->refEntity.axis[0] ); + RotateAroundDirection( ex->refEntity.axis, ang ); + } + } + + ex->startTime = cg.time - offset; + ex->endTime = ex->startTime + msec; + + // bias the time so all shader effects start correctly + //ex->refEntity.shaderTime = ex->startTime / 1000.0f; + ex->refEntity.shaderTime = ex->startTime * 0.001f; + + ex->refEntity.hModel = hModel; + ex->refEntity.customShader = shader; + ex->lifeRate = (float)numFrames / msec; + ex->leFlags = flags; + + //Scale the explosion + if (scale != 1) { + ex->refEntity.nonNormalizedAxes = qtrue; + + VectorScale( ex->refEntity.axis[0], scale, ex->refEntity.axis[0] ); + VectorScale( ex->refEntity.axis[1], scale, ex->refEntity.axis[1] ); + VectorScale( ex->refEntity.axis[2], scale, ex->refEntity.axis[2] ); + } + + // set origin + VectorCopy( newOrigin, ex->refEntity.origin ); + VectorCopy( newOrigin, ex->refEntity.oldorigin ); + + ex->color[0] = ex->color[1] = ex->color[2] = 1.0; + + return ex; +} + + + + +/* +================= +CG_Bleed + +This is the spurt of blood when a character gets hit +================= +*/ +/*void CG_Bleed( vec3_t origin, int entityNum ) { + localEntity_t *ex; + + if ( !cg_blood.integer ) { + return; + } + + ex = CG_AllocLocalEntity(); + ex->leType = LE_EXPLOSION; + + ex->startTime = cg.time; + ex->endTime = ex->startTime + 500; + + VectorCopy ( origin, ex->refEntity.origin); + ex->refEntity.reType = RT_SPRITE; + ex->refEntity.data.sprite.rotation = rand() % 360; + ex->refEntity.data.sprite.radius = 16; + + ex->refEntity.customShader = cgs.media.bloodExplosionShader; + + // don't show player's own blood in view + if ( entityNum == cg.snap->ps.clientNum ) { + ex->refEntity.renderfx |= RF_THIRD_PERSON; + } +}*/ + + + +/* +------------------------- +CG_ExplosionEffects + +Used to find the player and shake the camera if close enough +intensity ranges from 1 (minor tremble) to 16 (major quake) +------------------------- +*/ + +void CG_ExplosionEffects( vec3_t origin, int intensity, int radius) +{ + //FIXME: When exactly is the vieworg calculated in relation to the rest of the frame?s + + vec3_t dir; + float dist, intensityScale; + float realIntensity; + + VectorSubtract( cg.refdef.vieworg, origin, dir ); + dist = VectorNormalize( dir ); + + //Use the dir to add kick to the explosion + + if ( dist > radius ) + return; + + intensityScale = 1 - ( dist / (float) radius ); + realIntensity = intensity * intensityScale; + + CG_CameraShake( realIntensity, 500, qfalse ); +} + + +/* +================= +CG_Seeker +================= +*/ +/*void CG_Seeker( centity_t *cent ) +{ + refEntity_t re; + + vec3_t seekerOrg, viewAng; + float angle; + + + angle = cg.time/100.0f; + seekerOrg[0] = cent->lerpOrigin[0] + 18 * cos(angle); + seekerOrg[1] = cent->lerpOrigin[1] + 18 * sin(angle); + seekerOrg[2] = cent->lerpOrigin[2] + cg.predictedPlayerState.viewheight + 8 + (3*cos(cg.time/150.0f)); + + memset( &re, 0, sizeof( re ) ); + + re.reType = RT_MODEL; + VectorCopy ( seekerOrg, re.origin); + re.hModel = cgs.media.seekerModel; + re.shaderTime = (cg.time / 1000.0f); + + VectorCopy (cent->lerpAngles , viewAng); // so the seeker faces the same direction the player is + viewAng[0] = -90; // but, we don't want the seeker facing up or down, always horizontal + AnglesToAxis( viewAng, re.axis ); + VectorScale(re.axis[0], 0.5, re.axis[0]); + VectorScale(re.axis[1], 0.5, re.axis[1]); + VectorScale(re.axis[2], 0.5, re.axis[2]); + re.nonNormalizedAxes=qtrue; + + trap_R_AddRefEntityToScene( &re ); + +}*/ + +/* +------------------------- +CG_Smoke +TiM: Ported from EF SP +------------------------- +*/ + +//void CG_Smoke( vec3_t origin, vec3_t dir, float radius, float speed, qhandle_t shader ) +//{ +// vec3_t velocity/*, accel*/; +// int i; + +// for ( i = 0; i < 3; i++ ) +// { +// velocity[i] = dir[i] + ( 0.2f * crandom()); +// } + +// VectorScale( velocity, speed, velocity ); + //VectorScale( velocity, -0.25f, accel ); + //accel[2] = random() * 12.0f + 6.0f; + +// FX_AddSprite( origin, +// velocity, +// qfalse, //accel +// radius + (crandom() * radius * 0.5f ), +// radius + (crandom() * radius), +// 0.9f + crandom(), +// 0.0f, +// 16.0f + random() * 45.0f, +// 0.5f, +// 2000, +// shader ); //flags +//} + +qboolean SmokeThink( localEntity_t *le ) +{ + vec3_t velocity/*, accel*/; + vec3_t origin; + vec3_t dir; + float speed; + int i; + + VectorCopy( le->data.spawner.dir, dir ); + //clamp the smoke vector + //Smoke should always go up + dir[2] = Com_Clamp( 0.85f, 1.0f, dir[2] ); + + for ( i = 0; i < 3; i++ ) + { + velocity[i] = dir[i] + ( 0.2f * crandom()); + } + + VectorMA( le->refEntity.origin, 1, le->data.spawner.dir, origin); + + //slow down the smoke the smaller it gets + //else it scatters too much + speed = le->data.spawner.data1 * 2.4; + + VectorScale( velocity, speed, velocity ); //speed + //VectorScale( velocity, -0.25f, accel ); + //accel[2] = random() * 12.0f + 6.0f; + + FX_AddSprite( origin, + velocity, + qfalse, //accel + le->data.spawner.data1 + (crandom() * le->data.spawner.data1 * 0.5f ), + le->data.spawner.data1 + (crandom() * le->data.spawner.data1), + 0.8 /*+ crandom()*/, + 0.0, + 16.0f + random() * 45.0f, + 0.5f, + 7000, + cgs.media.smokeShader ); //flags + + return qtrue; +} + +/* +====================== +CG_Smoke + +Creates a smoke effect +====================== +*/ + +void CG_Smoke( vec3_t position, vec3_t dir, int killTime, int radius ) +{ + //CG_Printf( " %f %f %f\n", dir[0], dir[1], dir[2] ); + // give it a lifetime of 10 seconds because the refresh thinktime in g_fx.c is 10 seconds + FX_AddSpawner( position, dir, NULL, NULL, qfalse, 0, 0.15, killTime, SmokeThink, radius ); // +} + +/* +====================== +FireThink + +Engage fire effect +RPG-X | Marcin | 24/12/2008 +====================== +*/ +qboolean FireThink( localEntity_t *le ) +{ + vec3_t direction; + vec3_t origin; + + VectorCopy( le->data.spawner.dir, direction ); + VectorMA( le->refEntity.origin, 1, direction, origin ); + + origin[2] += 60.0f / (80.0f / le->data.spawner.data1); // extra offset + + FX_AddSprite( origin, + 0, + qfalse, + le->data.spawner.data1, + 0, + 1.0f, + 0.0f, + 0, + 0.5f, + 600, + cgs.media.fireShader ); + + return qtrue; +} + +/* +====================== +CG_Fire + +Creates a fire effect +RPG-X | Marcin | 24/12/2008 +====================== +*/ + +void CG_Fire( vec3_t position, vec3_t direction, int killTime, int radius, int fxEnt ) +{ + if(fxEnt) + FX_AddSpawner( position, direction, NULL, NULL, qfalse, 500, 0, killTime + 1000, FireThink, radius ); + else + FX_AddSpawner( position, direction, NULL, NULL, qfalse, 500, 0, killTime, FireThink, radius ); +} + +//localEntity_t *FX_AddSprite(vec3_t origin, vec3_t velocity, qboolean gravity, float scale, float dscale, +// float startalpha, float endalpha, float roll, float elasticity, +// float killTime, qhandle_t shader) + diff --git a/cgame/cg_ents.c b/cgame/cg_ents.c new file mode 100644 index 0000000..5ea8e38 --- /dev/null +++ b/cgame/cg_ents.c @@ -0,0 +1,1192 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_ents.c -- present snapshot entities, happens every single frame + +#include "cg_local.h" +#include "fx_local.h" + +static void CG_LaserSight( centity_t *cent ); // Laser +static void CG_Turbolift( centity_t *cent ); + +/* +====================== +CG_PositionEntityOnTag + +Modifies the entities position and axis by the given +tag location +====================== +*/ +void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ) { + int i; + orientation_t lerped; + + // lerp the tag + trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, + 1.0 - parent->backlerp, tagName ); + + // FIXME: allow origin offsets along tag? + VectorCopy( parent->origin, entity->origin ); + for ( i = 0 ; i < 3 ; i++ ) { + VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); + } + + // had to cast away the const to avoid compiler problems... + MatrixMultiply( lerped.axis, ((refEntity_t *)parent)->axis, entity->axis ); + entity->backlerp = parent->backlerp; + +} + + +/* +====================== +CG_PositionRotatedEntityOnTag + +Modifies the entities position and axis by the given +tag location +====================== +*/ +void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ) { + int i; + orientation_t lerped; + vec3_t tempAxis[3]; + +//AxisClear( entity->axis ); + // lerp the tag + trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, + 1.0 - parent->backlerp, tagName ); + + // FIXME: allow origin offsets along tag? + VectorCopy( parent->origin, entity->origin ); + for ( i = 0 ; i < 3 ; i++ ) { + VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); + } + + // had to cast away the const to avoid compiler problems... + MatrixMultiply( entity->axis, lerped.axis, tempAxis ); + MatrixMultiply( tempAxis, ((refEntity_t *)parent)->axis, entity->axis ); +} + + + +/* +========================================================================== + +FUNCTIONS CALLED EACH FRAME + +========================================================================== +*/ + +/* +====================== +CG_SetEntitySoundPosition + +Also called by event processing code +====================== +*/ +void CG_SetEntitySoundPosition( centity_t *cent ) { + if ( cent->currentState.solid == SOLID_BMODEL ) { + vec3_t origin; + float *v; + + v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; + VectorAdd( cent->lerpOrigin, v, origin ); + trap_S_UpdateEntityPosition( cent->currentState.number, origin ); + } else { + trap_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin ); + } +} + +/* +================== +CG_EntityEffects + +Add continuous entity effects, like local entity emission and lighting +================== +*/ +static void CG_EntityEffects( centity_t *cent ) { + + // update sound origins + CG_SetEntitySoundPosition( cent ); + + // add loop sound + if ( cent->currentState.loopSound ) { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + cgs.gameSounds[ cent->currentState.loopSound ] ); + } + + + // constant light glow + if ( cent->currentState.constantLight ) { + int cl; + int i, r, g, b; + + cl = cent->currentState.constantLight; + r = cl & 255; + g = ( cl >> 8 ) & 255; + b = ( cl >> 16 ) & 255; + i = ( ( cl >> 24 ) & 255 ) * 4; + trap_R_AddLightToScene( cent->lerpOrigin, i, r, g, b ); + } + +} + + +/* +================== +CG_Useable +================== +*/ +static void CG_Useable( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // if set to invisible, skip + if (!s1->modelindex) { + return; + } + + if (s1->modelindex == HI_SHIELD) + { // The portable shield should go through a different rendering function. + FX_DrawPortableShield(cent); + return; + } + + + memset (&ent, 0, sizeof(ent)); + + // set frame + + if (s1->eFlags & EF_ANIM_ALLFAST) + { + //ent.frame = (cg.time / 100); + ent.frame = (cg.time * 0.01); + ent.renderfx|=RF_WRAP_FRAMES; + } + else + { + ent.frame = s1->frame; + } + ent.oldframe = ent.frame; + ent.backlerp = 0; + + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + ent.hModel = cg_items[s1->modelindex2].model;//cgs.useableModels[s1->modelindex]; + + // player model + if (s1->number == cg.snap->ps.clientNum) { + ent.renderfx |= RF_THIRD_PERSON; // only draw from mirrors + } + + // convert angles to axis +// AnglesToAxis( cent->lerpAngles, ent.axis ); + { + // hack to keep dropped detpacks from rotating + vec3_t vecs[3]; + AngleVectors(s1->angles, vecs[0], vecs[1], vecs[2]); + VectorNegate(vecs[1], vecs[1]); + if (s1->modelindex == HI_DETPACK) // as stated, HACK for detpack + { + VectorScale(vecs[0], .5, vecs[0]); + VectorScale(vecs[1], .5, vecs[1]); + VectorScale(vecs[2], .5, vecs[2]); + } + AxisCopy( vecs, ent.axis ); + } + + // add to refresh list + trap_R_AddRefEntityToScene (&ent); +} + +/* +================== +CG_General +================== +*/ +#define ITEM_SCALEUP_DIV 1.0/(float)ITEM_SCALEUP_TIME +static void CG_General( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // if set to invisible, skip + if (!s1->modelindex) { + return; + } + + memset (&ent, 0, sizeof(ent)); + + // set frame + + if ( s1->eFlags & EF_ANIM_ONCE ) + { + ent.frame = s1->frame; + ent.renderfx|=RF_CAP_FRAMES; + } + else if (s1->eFlags & EF_ANIM_ALLFAST) + { + //ent.frame = (cg.time / 100); + ent.frame = (cg.time * 0.01); + ent.renderfx|=RF_WRAP_FRAMES; + } + else + { + ent.frame = s1->frame; + ent.renderfx|=RF_CAP_FRAMES; + } + + ent.oldframe = ent.frame; + ent.backlerp = 0; + + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + ent.hModel = cgs.gameModels[s1->modelindex]; + + // player model + if (s1->number == cg.snap->ps.clientNum) { + ent.renderfx |= RF_THIRD_PERSON; // only draw from mirrors + } + + // convert angles to axis + AnglesToAxis( cent->lerpAngles, ent.axis ); + + // add to refresh list + if ( s1->eFlags & EF_ITEMPLACEHOLDER ) // object is "spawning" in + { + int msec; + + if ( !cent->miscTime ) + { + cent->miscTime = cg.time; + } + + msec = cg.time - cent->miscTime; + if ( msec < ITEM_SCALEUP_TIME ) + { + float alpha; + int a; + alpha = (float)msec * ITEM_SCALEUP_DIV; + if ( s1->eventParm == 255 ) + { + alpha = 1.0f-alpha; + } + a = alpha * 255.0; + if (a <= 0) + a=1; + ent.shaderRGBA[0] = + ent.shaderRGBA[1] = + ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = a; + ent.renderfx |= RF_FORCE_ENT_ALPHA; + trap_R_AddRefEntityToScene(&ent); + ent.renderfx &= ~RF_FORCE_ENT_ALPHA; + + // Now draw the static shader over it. + // Alpha in over half the time, out over half. + alpha = sin(M_PI*alpha); + a = alpha * 255.0; + if (a <= 0) + a=1; + ent.customShader = cgs.media.rezOutShader; + ent.shaderRGBA[0] = + ent.shaderRGBA[1] = + ent.shaderRGBA[2] = a; + trap_R_AddRefEntityToScene( &ent ); + } + else + { + trap_R_AddRefEntityToScene (&ent); + } + } + else + { + cent->miscTime = 0; + trap_R_AddRefEntityToScene (&ent); + } +} + + +/* +================== +CG_Speaker + +Speaker entities can automatically play sounds +================== +*/ +static void CG_Speaker( centity_t *cent ) { + if ( ! cent->currentState.clientNum ) { // FIXME: use something other than clientNum... + return; // not auto triggering + } + + if ( cg.time < cent->miscTime ) { + return; + } + + trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[cent->currentState.eventParm] ); + + // ent->s.frame = ent->wait * 10; + // ent->s.clientNum = ent->random * 10; + cent->miscTime = cg.time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * crandom(); +} + +/* +================== +CG_Item +================== +*/ +static void CG_Item( centity_t *cent ) { + refEntity_t ent; + entityState_t *es; + gitem_t *item; + int msec; +// float scale; + // RPG-X: Marcin: Custom angles for each weapon so they lie on the ground correctly. - 06/12/2008 + const vec3_t weaponangles[WP_NUM_WEAPONS] = { + { 0, 0, 0 }, // WP_NONE + { 0, 0, 0 }, // WP_NULL_HAND + { 52, 280, 18 }, // WP_TRICORDER + { 48, 26, 33 }, // WP_PADD + { 335, 210, 347 }, // WP_COFFEE + { 15, 160, 65 }, // WP_PHASER + { 5, 10, 70 }, // WP_COMPRESSION_RIFLE + { 5, 6, 70 }, // WP_TR116 + { 5, 17, 70 }, // WP_GRENADE_LAUNCHER + { 350, 23, 70 }, // WP_QUANTUM_BURST + { 15, 187, 80 }, // WP_DISRUPTOR + { 0, 270, 86 }, // WP_MEDKIT + { 0, 247, 90 }, // WP_VOYAGER_HYPO + { 36, 190, 40 }, // WP_DERMAL_REGEN + { 0, 0, 105 }, // WP_TOOLKIT + { 0, 210, 90 } // WP_HYPERSPANNER + }; + + es = ¢->currentState; + if ( es->modelindex >= bg_numItems ) { + CG_Error( "Bad item index %i on entity", es->modelindex ); + } + + // if set to invisible, skip + if ( !es->modelindex || ( es->eFlags & EF_NODRAW ) ) { + return; + } + + item = &bg_itemlist[ es->modelindex ]; + if ( cg_simpleItems.integer && item->giType != IT_TEAM ) { + memset( &ent, 0, sizeof( ent ) ); + ent.reType = RT_SPRITE; + VectorCopy( cent->lerpOrigin, ent.origin ); + ent.data.sprite.radius = 14; + + ent.customShader = cg_items[es->modelindex].icon; + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 255; + + if ( es->eFlags & EF_ITEMPLACEHOLDER ) + { + ent.renderfx |= RF_FORCE_ENT_ALPHA; + ent.shaderRGBA[3] = 50 + sin(cg.time*0.01)*30; + } + else + { + ent.shaderRGBA[3] = 255; + } + + trap_R_AddRefEntityToScene(&ent); + + return; + } + + // items bob up and down continuously +// scale = 0.005 + cent->currentState.number * 0.00001; +// cent->lerpOrigin[2] += 4 + cos( ( cg.time + 1000 ) * scale ) * 4; + + memset (&ent, 0, sizeof(ent)); + + + // autorotate at one of two speeds + if ( item->giType == IT_HEALTH ) + { + VectorCopy( cg.autoAnglesFast, cent->lerpAngles ); + AxisCopy( cg.autoAxisFast, ent.axis ); + } + else if (item->giType != IT_TEAM) // RPG-X | Marcin | 05/12/2008 + { + //VectorCopy( cg.autoAngles, cent->lerpAngles ); + //AxisCopy( cg.autoAxis, ent.axis ); + VectorCopy( weaponangles[item->giTag], cent->lerpAngles ); + AnglesToAxis( weaponangles[item->giTag], ent.axis); + } + else + { // Flags don't rotate at all... + float frame; + vec3_t vecs[3]; + + + // ...but they do animate. + //frame = (cg.time / 100.0); + frame = (cg.time * 0.01); + ent.renderfx|=RF_WRAP_FRAMES; + + ent.oldframe = (int)frame; + ent.frame = (int)frame+1; + ent.backlerp = (float)(ent.frame) - frame; + + // and they are scaled too + if (1) + { + AngleVectors(es->angles, vecs[0], vecs[1], vecs[2]); + VectorScale( vecs[0], 1.6, vecs[0] ); + VectorScale( vecs[1], 1.6, vecs[1] ); + VectorScale( vecs[2], 1.6, vecs[2] ); + AxisCopy( vecs, ent.axis ); + } + else + { + AnglesToAxis(cent->lerpAngles, ent.axis); + VectorScale( ent.axis[0], 1.6, ent.axis[0] ); + VectorScale( ent.axis[1], 1.6, ent.axis[1] ); + VectorScale( ent.axis[2], 1.6, ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; + } + if (item->giTag == PW_BORG_ADAPT) + { + //ent.customShader = cgs.media.blueFlagShader[3]; + } + else + { + ent.customShader = cgs.media.redFlagShader[3]; + } + } + + // the weapons have their origin where they attatch to player + // models, so we need to offset them or they will rotate + // eccentricly + if ( item->giType == IT_WEAPON ) { + weaponInfo_t *wi; + + 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]; + + cent->lerpOrigin[2] -= 14; // an extra height boost + // RPG-X | Marcin | 03/12/2008 + // (Was += 8) + } + + ent.hModel = cg_items[es->modelindex].model; + + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + ent.nonNormalizedAxes = qfalse; + + if ( es->eFlags & EF_ITEMPLACEHOLDER ) // item has been picked up + { + if ( es->eFlags & EF_DEAD ) // if item had been droped, don't show at all + return; + + ent.customShader = cgs.media.weaponPlaceholderShader; + } + + // RPG-X | Marcin | 06/12/2008 + // increase the size of the weapons when they are presented as items - DON'T! + /*if ( item->giType == IT_WEAPON ) { + VectorScale( ent.axis[0], 1.5, ent.axis[0] ); + VectorScale( ent.axis[1], 1.5, ent.axis[1] ); + VectorScale( ent.axis[2], 1.5, ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; + }*/ + + msec = cg.time - cent->miscTime; // Count from last respawn. + /*if (cg.predictedPlayerState.introTime > cg.time) + { // The stuff is "holodecking in". + int dtime; + + dtime = cg.predictedPlayerState.introTime - cg.time; + if (dtime < TIME_FADE_DUR) + { // "rez" in. + float alpha; + int a; + + alpha = 1.0 - ((float)dtime / (float)TIME_FADE_DUR); + a = alpha * 255.0; + if (a <= 0) + a=1; + ent.shaderRGBA[3] = a; + ent.renderfx |= RF_FORCE_ENT_ALPHA; + trap_R_AddRefEntityToScene(&ent); + ent.renderfx &= ~RF_FORCE_ENT_ALPHA; + + // Now draw the static shader over it. + // Alpha in over half the time, out over half. + alpha = sin(M_PI*alpha); + a = alpha * 255.0; + if (a <= 0) + a=1; + ent.customShader = cgs.media.rezOutShader; + ent.shaderRGBA[0] = + ent.shaderRGBA[1] = + ent.shaderRGBA[2] = a; + trap_R_AddRefEntityToScene( &ent ); + ent.shaderRGBA[0] = + ent.shaderRGBA[1] = + ent.shaderRGBA[2] = 255; + } + } + else*/ if (item->giType != IT_TEAM && msec >= 0 && msec < ITEM_SCALEUP_TIME && !(es->eFlags & EF_ITEMPLACEHOLDER)) + { // if just respawned, fade in, but don't do this for flags. + float alpha; + int a; + + alpha = (float)msec * ITEM_SCALEUP_DIV; + a = alpha * 255.0; + if (a <= 0) + a=1; + ent.shaderRGBA[3] = a; + ent.renderfx |= RF_FORCE_ENT_ALPHA; + trap_R_AddRefEntityToScene(&ent); + ent.renderfx &= ~RF_FORCE_ENT_ALPHA; + + // Now draw the static shader over it. + // Alpha in over half the time, out over half. + alpha = sin(M_PI*alpha); + a = alpha * 255.0; + if (a <= 0) + a=1; + ent.customShader = cgs.media.rezOutShader; + ent.shaderRGBA[0] = + ent.shaderRGBA[1] = + ent.shaderRGBA[2] = a; + trap_R_AddRefEntityToScene( &ent ); + //what is the point of this next bit??? + ent.shaderRGBA[0] = + ent.shaderRGBA[1] = + ent.shaderRGBA[2] = 255; + } + else + { // add to refresh list -- normal item + trap_R_AddRefEntityToScene(&ent); + } +} + +//============================================================================ + +/* +=============== +CG_Missile +=============== +*/ +static void CG_Missile( centity_t *cent, qboolean altfire ) { + refEntity_t ent; + entityState_t *s1; + qhandle_t missile = 0; + const weaponInfo_t *weapon; + int rpg_tripmines; + const char *info; + + 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 (cent->currentState.eFlags & EF_ALT_FIRING) + if (altfire) + { + // add trails + if ( weapon->alt_missileTrailFunc ) + { + weapon->alt_missileTrailFunc( cent, weapon ); + } + + // add dynamic light + if ( weapon->alt_missileDlight ) { + trap_R_AddLightToScene(cent->lerpOrigin, weapon->alt_missileDlight, + weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2] ); + } + + // add missile sound + if ( weapon->alt_missileSound ) + { + vec3_t velocity; + + BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); + if (velocity[0] || velocity[1] || velocity[2]) + { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->alt_missileSound ); + } + } + //RPG-X: RedTechie - non-admin see no tripmines! But first by popular demand check CVAR + info = CG_ConfigString( CS_SERVERINFO ); + rpg_tripmines = atoi( Info_ValueForKey( info, "rpg_invisibletripmines" ) ); + if (!weapon->alt_missileModel || (!cgs.clientinfo[cg.snap->ps.clientNum].isAdmin/*cg.snap->ps.persistant[PERS_CLASS] != PC_ADMIN*/ && rpg_tripmines == 1)) { + //if there is no missile then we're done + return; + } + missile = weapon->alt_missileModel; + } + else + { + if (cent->thinkFlag) + { // we already grabbed info that was stored on the game side, so use what's already in cent->rawAngles + //and cent->rawOrigin + } + else + { + // kef -- get out some info we stored in a very unfortunate manner on the game side + VectorCopy(cent->currentState.angles2,cent->rawAngles); + VectorCopy(cent->currentState.angles2,cent->rawOrigin); + cent->thinkFlag = 1; + } + // add trails + if ( weapon->missileTrailFunc ) + { + weapon->missileTrailFunc( cent, weapon ); + } + + // add dynamic light + if ( weapon->missileDlight ) { + trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, + weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2] ); + } + + // add missile sound +/* if ( weapon->missileSound ) { + vec3_t velocity; + + BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); + + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->missileSound ); + } +*/ + if (!weapon->missileModel) { //if ther is no missile then we're done + return; + } + missile = weapon->missileModel; + } + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + if ( cent->currentState.weapon == WP_QUANTUM_BURST ) { + ent.reType = RT_SPRITE; + ent.data.sprite.radius = 16; + ent.data.sprite.rotation = 0; + trap_R_AddRefEntityToScene( &ent ); + return; + } + + // flicker between two skins + ent.skinNum = cg.clientFrame & 1; + ent.hModel = missile; + ent.renderfx = RF_NOSHADOW; + + if ( s1->pos.trType == TR_STATIONARY ) + { + AnglesToAxis( s1->angles, ent.axis ); + } + else + { + // 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 && ( cent->currentState.weapon != WP_DISRUPTOR || cent->currentState.weapon != WP_DISRUPTOR ) ) { //RPG-X-TiM: Stop from spinning O_o I got dizzy + //RotateAroundDirection( ent.axis, cg.time / 4 ); + RotateAroundDirection( ent.axis, cg.time * 0.25); + } else { + RotateAroundDirection( ent.axis, s1->time ); + } + } + + // add to refresh list, possibly with quad glow + CG_AddRefEntityWithPowerups( &ent, s1->powerups, s1->eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, qfalse ); + if ( s1->eFlags & EF_FIRING ) + {//special code for adding the beam to the attached tripwire mine + trace_t trace; + vec3_t beamOrg, beamEnd, rgb; + float alpha; + qhandle_t flareShader; + + if ( s1->otherEntityNum2 == TEAM_BLUE ) + { + VectorSet( rgb, 0.0f, 0.3f, 1.0f ); + alpha = 1.0f - (random() * 0.2); + flareShader = cgs.media.blueParticleShader; + } + else + { + VectorSet( rgb, 1.0f, 0.0f, 0.0f ); + alpha = 1.0f - (random() * 0.5); + flareShader = cgs.media.borgEyeFlareShader; + } + VectorCopy( ent.origin, beamOrg ); + VectorMA( beamOrg, -2, ent.axis[0], beamOrg );//forward + VectorMA( beamOrg, -1022, ent.axis[0], beamEnd);//forward to end, have to reverse it because it actually faces other way + + trap_CM_BoxTrace( &trace, beamOrg, beamEnd, NULL, NULL, 0, MASK_SHOT ); + VectorCopy(trace.endpos, beamEnd); + FX_AddLine2( beamOrg, beamEnd, 1.0f, 0.35f + ( crandom() * 0.1 ), 0.0f, 0.35f + ( crandom() * 0.1 ), 0.0f, alpha, alpha, rgb, rgb,1.0f, cgs.media.whiteLaserShader ); + //FX_AddSprite( beamOrg, NULL, qfalse, 1.0f + (random() * 2.0f), 0.0f, 0.9f, 0.9f, 0.0f, 0.0f, 0.0f, flareShader ); + FX_AddQuad( beamOrg, ent.axis[0], 1.0f, 1.0f, 2.0f + (crandom() * 1.0f), 0.0f, 0.0f, 1.0f, flareShader ); + FX_AddQuad( beamEnd, trace.plane.normal, 1.0f, 1.0f, 2.0f + (crandom() * 1.0f), 0.0f, 0.0f, 1.0f, flareShader ); + } +} + +/* +=============== +CG_Mover +=============== +*/ +static void CG_Mover( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + AnglesToAxis( cent->lerpAngles, ent.axis ); + + ent.renderfx = RF_NOSHADOW; + + // flicker between two skins (FIXME?) + ent.skinNum = ( 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; + + // add to refresh list + trap_R_AddRefEntityToScene(&ent); +} + + +/* +RPG-X: RedTechie - FIXME: STILL FRICKEN SWAYS its not a sailing ship its a fricken star ship! +=============== +CG_Portal +=============== +*/ +static void CG_Portal( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( s1->origin2, ent.oldorigin ); + ByteToDir( s1->eventParm, ent.axis[0] ); + PerpendicularVector( ent.axis[1], ent.axis[0] ); + + // negating this tends to get the directions like they want + // we really should have a camera roll value + VectorSubtract( vec3_origin, ent.axis[1], ent.axis[1] ); + + CrossProduct( ent.axis[0], ent.axis[1], ent.axis[2] ); + ent.reType = RT_PORTALSURFACE; + + ent.frame = s1->frame; // rotation speed - s1->frame + ent.skinNum = s1->clientNum / 256 * 360; // roll offset //RPG-X: RedTechie - ent.skinNum = s1->clientNum/256.0 * 360; + //ent.oldframe = 0; + + // 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_entities[ moverNum ]; + if ( cent->currentState.eType != ET_MOVER && cent->currentState.eType != ET_MOVER_STR ) { //RPG-X | GSIO01 | 13/05/2009 + 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, next; + float f; + + // it would be an internal error to find an entity that interpolates without + // a snapshot ahead of the current one + if ( cg.nextSnap == NULL ) { + CG_Error( "CG_InterpoateEntityPosition: cg.nextSnap == NULL" ); + } + + f = cg.frameInterpolation; + + // this will linearize a sine or parabolic curve, but it is important + // to not extrapolate player positions if more recent data is available + BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, current ); + BG_EvaluateTrajectory( ¢->nextState.pos, cg.nextSnap->serverTime, next ); + + cent->lerpOrigin[0] = current[0] + f * ( next[0] - current[0] ); + cent->lerpOrigin[1] = current[1] + f * ( next[1] - current[1] ); + cent->lerpOrigin[2] = current[2] + f * ( next[2] - current[2] ); + + BG_EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, current ); + BG_EvaluateTrajectory( ¢->nextState.apos, cg.nextSnap->serverTime, next ); + + cent->lerpAngles[0] = LerpAngle( current[0], next[0], f ); + cent->lerpAngles[1] = LerpAngle( current[1], next[1], f ); + cent->lerpAngles[2] = LerpAngle( current[2], next[2], f ); + +} + +/* +=============== +CG_CalcEntityLerpPositions + +=============== +*/ +static void CG_CalcEntityLerpPositions( centity_t *cent ) { + if ( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE ) { + CG_InterpolateEntityPosition( cent ); + return; + } + + // just use the current frame and evaluate as best we can + BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); + + // adjust for riding a mover if it wasn't rolled into the predicted + // player state + if ( cent != &cg.predictedPlayerEntity ) { + CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum, + cg.snap->serverTime, cg.time, cent->lerpOrigin ); + } +} + +/* +=============== +CG_AddCEntity + +=============== +*/ +static void CG_AddCEntity( centity_t *cent ) { + // event-only entities will have been dealt with already + if ( cent->currentState.eType >= ET_EVENTS ) { + return; + } + + // calculate the current origin + CG_CalcEntityLerpPositions( cent ); + + // add automatic effects + CG_EntityEffects( cent ); + + switch ( cent->currentState.eType ) { + default: + CG_Error( "Bad entity type: %i\n", cent->currentState.eType ); + break; + case ET_TRIC_STRING: + case ET_INVISIBLE: + case ET_PUSH_TRIGGER: + case ET_TELEPORT_TRIGGER: + break; + case ET_USEABLE: // e.g. detpacks + CG_Useable( cent ); + break; + case ET_GENERAL: + CG_General( cent ); + break; + case ET_PLAYER: + CG_Player( cent ); + break; + case ET_ITEM: + CG_Item( cent ); + break; + case ET_ALT_MISSILE: + CG_Missile( cent, qtrue ); + break; + case ET_MISSILE: + CG_Missile( cent, qfalse ); + break; + case ET_MOVER: + case ET_MOVER_STR: + 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_LASER: + CG_LaserSight( cent ); + break; + case ET_TURBOLIFT: + CG_Turbolift( cent ); + break; + } +} + +/* +=============== +CG_AddPacketEntities + +=============== +*/ +void CG_AddPacketEntities( void ) { + int num; + centity_t *cent; + playerState_t *ps; + + // set cg.frameInterpolation + if ( cg.nextSnap ) { + int delta; + + delta = (cg.nextSnap->serverTime - cg.snap->serverTime); + if ( delta == 0 ) { + cg.frameInterpolation = 0; + } else { + cg.frameInterpolation = (float)( cg.time - cg.snap->serverTime ) / delta; + } + } else { + cg.frameInterpolation = 0; // actually, it should never be used, because + // no entities should be marked as interpolating + } + + + // the auto-rotating items will all have the same axis + + // RPG-X | Marcin | 03/12/2008 + // We don't want dropped weapons to rotate at all + // -- NOT USED ANY MORE THOUGH -- + cg.autoAngles[0] = 0; + cg.autoAngles[1] = 0; + cg.autoAngles[2] = 68; + + cg.autoAnglesFast[0] = 0; + cg.autoAnglesFast[1] = ( cg.time & 1023 ) * 360 / 1024; + cg.autoAnglesFast[2] = 0; + + AnglesToAxis( cg.autoAngles, cg.autoAxis ); + AnglesToAxis( cg.autoAnglesFast, cg.autoAxisFast ); + + // generate and add the entity from the playerstate + ps = &cg.predictedPlayerState; + BG_PlayerStateToEntityState( ps, &cg.predictedPlayerEntity.currentState, qfalse ); + CG_AddCEntity( &cg.predictedPlayerEntity ); + + // lerp the non-predicted value for lightning gun origins + CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] ); + + // add each entity sent over by the server + for ( num = 0 ; num < cg.snap->numEntities ; num++ ) { + cent = &cg_entities[ cg.snap->entities[ num ].number ]; + CG_AddCEntity( cent ); + } +} + +/* +================== +CG_LaserSight + Creates the laser +================== +*/ + +static void CG_LaserSight( centity_t *cent ) { + refEntity_t ent; + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + if (cent->currentState.eventParm == 1) + { + ent.reType = RT_SPRITE; + //ent.radius = 2; + //ent.rotation = 0; + ent.data.sprite.radius = 2; + ent.customShader = cgs.media.laserShader; + trap_R_AddRefEntityToScene( &ent ); + } + else { + trap_R_AddLightToScene(ent.origin, 200, 1, 1, 1); + } + + +} + +/* +================== +CG_Turbolift +A client complement +to the turbolift ent, +this plays the sound +FX whilst it is in action +================== +*/ + +static void CG_Turbolift( centity_t* cent ) +{ + int i; + centity_t *player; + //TiM - find all of the cents inside the lift, and make it so they orient to their view angles. + //Otherwise, when they teleport, the entire body snaps around, looking weird and/or painful. + + if ( cent->currentState.time2 <= 0 ) + return; + + for ( i = 0; i < MAX_CLIENTS; i++ ) + { + player = &cg_entities[i]; + + if ( !player ) + continue; + + if ( ( player->lerpOrigin[0] > cent->currentState.angles2[0] && player->lerpOrigin[0] < cent->currentState.origin2[0] ) + && ( player->lerpOrigin[1] > cent->currentState.angles2[1] && player->lerpOrigin[1] < cent->currentState.origin2[1] ) + && ( player->lerpOrigin[2] > cent->currentState.angles2[2] && player->lerpOrigin[2] < cent->currentState.origin2[2] ) ) + { + //time2 = startTime+waittime + if ( cent->currentState.time2 > 0 ) + { + cg_liftEnts[player->currentState.clientNum] = cent->currentState.time2; + } + else + { + cg_liftEnts[player->currentState.clientNum] = 0; + } + } + } + + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, //cent->lerpOrigin + cgs.gameSounds[ cent->currentState.loopSound ] ); +} + +/*static void CG_Turbolift( centity_t* cent ) +{ + if ( cent->currentState.eventParm <= 0 ) + { + cent->deathTime = 0; + cent->miscTime = 0; + return; + } + + //Init sound + if ( cent->miscTime == 0 ) + { + cent->miscTime = cg.time + cent->currentState.modelindex2; //set the end of the wait time + } + + if ( cg.time < cent->miscTime ) + { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, //cent->lerpOrigin + cgs.gameSounds[ cent->currentState.loopSound ] ); + + return; + } + + if ( cent->deathTime == 0 ) + { + trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[cent->currentState.otherEntityNum2] ); + cent->deathTime = 1; + } +}*/ diff --git a/cgame/cg_env.c b/cgame/cg_env.c new file mode 100644 index 0000000..6eadb7b --- /dev/null +++ b/cgame/cg_env.c @@ -0,0 +1,1178 @@ +//This file contains environmental effects for the designers + +#include "cg_local.h" +#include "fx_local.h" + +// these flags should be synchronized with the spawnflags in g_fx.c for fx_bolt +#define BOLT_SPARKS (1<<0) +#define BOLT_BORG (1<<1) + + +qboolean SparkThink( localEntity_t *le ) +{ + vec3_t dir, direction, start, end; + vec3_t velocity; + float scale = 0, alpha = 0; + int numSparks = 0, i = 0, j = 0; + sfxHandle_t snd = cgs.media.envSparkSound1; + + switch(irandom(1, 3)) + { + case 1: + snd = cgs.media.envSparkSound1; + break; + case 2: + snd = cgs.media.envSparkSound2; + break; + case 3: + snd = cgs.media.envSparkSound3; + break; + } + trap_S_StartSound (le->refEntity.origin, ENTITYNUM_WORLD, CHAN_BODY, snd ); + + CG_InitLensFlare( le->refEntity.origin, + 40, 40, + colorTable[CT_YELLOW], 1.2, 2.0, 1600, 200, + colorTable[CT_YELLOW], 1600, 200, 600, 6, qtrue, + 0, 0, qfalse, qtrue, + qtrue, 0.7, cg.time, 90, 0, 300); + + + VectorCopy(le->data.spawner.dir, dir); + + //AngleVectors( dir, dir, NULL, NULL ); + for ( j = 0; j < 3; j ++ ) + direction[j] = dir[j] + (0.25f * crandom()); + + VectorNormalize( direction ); + + //trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgi_S_RegisterSound( va( "sound/world/ric%d.wav", (rand() & 2)+1) ) ); + + numSparks = 8 + (random() * 4.0f); + + scale = 0.2f + (random() *0.4); + VectorMA( le->refEntity.origin, 24.0f + (crandom() * 4.0f), dir, end ); + + //One long spark + FX_AddLine( le->refEntity.origin, + end, + 1.0f, + scale, + 0.0f, + 1.0f, + 0.25f, + 125.0f, + cgs.media.sparkShader ); + + for ( i = 0; i < numSparks; i++ ) + { + scale = 0.2f + (random() *0.4); + + for ( j = 0; j < 3; j ++ ) + direction[j] = dir[j] + (0.25f * crandom()); + + VectorNormalize(direction); + + VectorMA( le->refEntity.origin, 0.0f + ( random() * 2.0f ), direction, start ); + VectorMA( start, 2.0f + ( random() * 16.0f ), direction, end ); + + FX_AddLine( start, + end, + 1.0f, + scale, + 0.0f, + 1.0f, + 0.25f, + 125.0f, + cgs.media.sparkShader ); + } + + if ( rand() & 1 ) + { + numSparks = 1 + (random() * 2.0f); + for ( i = 0; i < numSparks; i++ ) + { + scale = 0.5f + (random() * 0.5f); + + VectorScale( direction, 250, velocity ); + + FX_AddTrail( start, + velocity, + qtrue, + 8.0f, + -32.0f, + scale, + -scale, + 1.0f, + 0.5f, + 0.25f, + 700.0f, + cgs.media.sparkShader); + + } + } + + VectorMA( le->refEntity.origin, 1, dir, direction ); + + scale = 6.0f + (random() * 8.0f); + alpha = 0.1 + (random() * 0.4f); + + VectorSet( velocity, 0, 0, 8 ); + + FX_AddSprite( direction, + velocity, + qfalse, + scale, + scale, + alpha, + 0.0f, + random()*45.0f, + 0.0f, + 1000.0f, + cgs.media.steamShader ); + + return qtrue; +} + +/* +====================== +CG_Spark + +Creates a spark effect +====================== +*/ + +void CG_Spark( vec3_t origin, vec3_t normal, int delay, int killTime ) +{ + // give it a lifetime of 10 seconds because the refresh thinktime in g_fx.c is 10 seconds + FX_AddSpawner( origin, normal, NULL, NULL, qfalse, delay, 1.5, killTime, SparkThink, 100 ); //10000 + +} + + + +qboolean SteamThink( localEntity_t *le ) +{ + float speed = 200; + vec3_t direction; + vec3_t velocity = { 0, 0, 128 }; + float scale, dscale; + vec3_t origin; + + //FIXME: Whole lotta randoms... + + VectorCopy( le->data.spawner.dir, direction ); + //AngleVectors( direction, direction, NULL, NULL ); + //VectorNormalize(direction); + + //TiM : Offset by 1. sometimes it can spawn in walls and the particles act weird then + VectorMA( le->refEntity.origin, 1, direction, origin ); + + direction[0] += (direction[0] * crandom() * le->data.spawner.variance); + direction[1] += (direction[0] * crandom() * le->data.spawner.variance); + direction[2] += (direction[0] * crandom() * le->data.spawner.variance); + + VectorScale( direction, speed, velocity ); + + scale = 4.0f + (random()); + dscale = scale * 4.0; + + FX_AddSprite( origin, + velocity, + qfalse, + scale, + dscale, + 1.0f, + 0.0f, + random() * 360, + 0.25f, + 300,//(len / speed) * 1000, + cgs.media.steamShader ); + + return qtrue; +} + +/* +====================== +CG_Steam + +Creates a steam effect +====================== +*/ + +void CG_Steam( vec3_t position, vec3_t dir, int killTime ) +{ + // give it a lifetime of 10 seconds because the refresh thinktime in g_fx.c is 10 seconds + FX_AddSpawner( position, dir, NULL, NULL, qfalse, 0, 0.15, killTime, SteamThink, 100 ); // +} + +/* +====================== +CG_Bolt + +Creates a electricity bolt effect +====================== +*/ +#define DATA_EFFECTS 0 +#define DATA_CHAOS 1 +#define DATA_RADIUS 2 + +//----------------------------- +void BoltSparkSpew( vec3_t origin, vec3_t normal ) +{ + float scale = 1.0f + ( random() * 1.0f ); + int num = 0, i = 0; + vec3_t vel; + + trap_R_AddLightToScene( origin, 75 + (rand()&31), 1.0, 0.8, 1.0 ); + + // Drop some sparks + num = (int)(random() * 2) + 2; + + for ( i = 0; i < num; i++ ) + { + scale = 0.6f + random(); + if ( rand() & 1 ) + FXE_Spray( normal, 70, 80, 0.9f, vel); + else + FXE_Spray( normal, 80, 200, 0.5f, vel); + + FX_AddTrail( origin, vel, qfalse, 8.0f + random() * 8, -48.0f, + scale, -scale, 1.0f, 0.8f, 0.4f, 600.0f, cgs.media.spark2Shader ); + } +} + + +qboolean BoltFireback( localEntity_t *le) +{ +//localEntity_t *FX_AddElectricity( vec3_t origin, vec3_t origin2, float stScale, float scale, float dscale, +// float startalpha, float endalpha, float killTime, qhandle_t shader, float deviation ); + float killTime = (0 == le->data.spawner.delay)?10000:200; + + FX_AddElectricity(le->refEntity.origin, le->data.spawner.dir, 0.2f, 15.0, -15.0, 1.0, 0.5, killTime, + cgs.media.bolt2Shader, le->data.spawner.variance ); + // is this spawner on a random delay? + if (le->data.spawner.data1) + { + le->data.spawner.delay = flrandom(0,5000); + } + return qtrue; +} + +//----------------------------- +qboolean BorgBoltFireback( localEntity_t *le) +{ + float killTime = (0 == le->data.spawner.delay)?10000:200; + + FX_AddElectricity(le->refEntity.origin, le->data.spawner.dir, 0.2f, 15.0, -5.0, 1.0, 0.5, killTime, + cgs.media.borgLightningShaders[1], le->data.spawner.variance ); + // is this spawner on a random delay? + if (le->data.spawner.data1) + { + le->data.spawner.delay = flrandom(0,5000); + } + return qtrue; +} + +//----------------------------- +qboolean BoltFirebackSparks( localEntity_t *le) +{ + vec3_t dir; + float killTime = (0 == le->data.spawner.delay)?10000:200; + + VectorSubtract(le->refEntity.origin, le->data.spawner.dir, dir); + VectorNormalize(dir); + FX_AddElectricity(le->refEntity.origin, le->data.spawner.dir, 0.2f, 15.0, -15.0, 1.0, 0.5, killTime, + cgs.media.bolt2Shader, le->data.spawner.variance ); + BoltSparkSpew(le->data.spawner.dir, dir); + // is this spawner on a random delay? + if (le->data.spawner.data1) + { + le->data.spawner.delay = flrandom(0,5000); + } + return qtrue; +} + +//----------------------------- +qboolean BorgBoltFirebackSparks( localEntity_t *le) +{ + vec3_t dir; + float killTime = (0 == le->data.spawner.delay)?10000:200; + + VectorSubtract(le->refEntity.origin, le->data.spawner.dir, dir); + VectorNormalize(dir); + FX_AddElectricity(le->refEntity.origin, le->data.spawner.dir, 0.2f, 15.0, -15.0, 1.0, 0.5, killTime, + cgs.media.borgLightningShaders[0], le->data.spawner.variance ); + BoltSparkSpew(le->data.spawner.dir, dir); + // is this spawner on a random delay? + if (le->data.spawner.data1) + { + le->data.spawner.delay = flrandom(0,5000); + } + return qtrue; +} + +//----------------------------- +void CG_Bolt( centity_t *cent ) +{ + localEntity_t *le = NULL; + qboolean bSparks = cent->currentState.eventParm & BOLT_SPARKS; + qboolean bBorg = cent->currentState.eventParm & BOLT_BORG; + float radius = cent->currentState.angles2[0], chaos = cent->currentState.angles2[1]; + float delay = cent->currentState.time2 * 1000; // the value given by the designer is in seconds + qboolean bRandom = qfalse; + + if (delay < 0) + { + // random + delay = flrandom(0.1, 5000.0); + bRandom = qtrue; + } + if (delay > 10000) + { + delay = 10000; + } + + if ( bBorg ) + { + if (bSparks) + { + le = FX_AddSpawner( cent->lerpOrigin, cent->currentState.origin2, NULL, NULL, qfalse, delay, + chaos, 10000, BorgBoltFirebackSparks, radius ); + } + else + { + le = FX_AddSpawner( cent->lerpOrigin, cent->currentState.origin2, NULL, NULL, qfalse, delay, + chaos, 10000, BorgBoltFireback, radius ); + } + } + else + { + if (bSparks) + { + le = FX_AddSpawner( cent->lerpOrigin, cent->currentState.origin2, NULL, NULL, qfalse, delay, + chaos, 10000, BoltFirebackSparks, radius ); + } + else + { + le = FX_AddSpawner( cent->lerpOrigin, cent->currentState.origin2, NULL, NULL, qfalse, delay, + chaos, 10000, BoltFireback, radius ); + } + } + if (bRandom) + { + le->data.spawner.data1 = 1; + } +} + +void CG_TransporterPad(vec3_t origin) +{ + FX_TransporterPad(origin); +} + +/* +=========================== +Drip + +Create timed drip effect +=========================== +*/ + +qboolean DripCallback( localEntity_t *le ) +{ + localEntity_t *trail = NULL; + qhandle_t shader = 0; + + switch (le->data.spawner.data1) + { + case 1: + shader = cgs.media.oilDropShader; + break; + case 2: + shader = cgs.media.greenDropShader; + break; + case 0: + default: + shader = cgs.media.waterDropShader; + break; + } + trail = FX_AddTrail(le->refEntity.origin, le->pos.trDelta, qfalse, 4, -2, 1, 0, 0.8, 0.4, 0.0, + 300, shader); + trail->leFlags |= LEF_ONE_FRAME; + + return qtrue; +} + +//------------------------------------------------------------------------------ +qboolean DripSplash( localEntity_t *le ) +{ + float scale = 1.0f + ( random() * 1.0f ); + int num = 0, i = 0; + vec3_t vel, normal, origin; + qhandle_t shader = 0; + + switch (le->data.spawner.data1) + { + case 1: + shader = cgs.media.oilDropShader; + break; + case 2: + shader = cgs.media.greenDropShader; + break; + case 0: + default: + shader = cgs.media.waterDropShader; + break; + } + + VectorCopy(le->data.spawner.dir, normal); + VectorCopy(le->refEntity.origin, origin); + + // splashing water droplets. which, I'm fairly certain, is an alternative band from Europe. + num = (int)(random() * 2) + 6; + + for ( i = 0; i < num; i++ ) + { + scale = 0.6f + random(); + if ( rand() & 1 ) + FXE_Spray( normal, 110, 80, 0.9f, vel); + else + FXE_Spray( normal, 150, 150, 0.5f, vel); + + FX_AddTrail( origin, vel, qtrue, 4.0f, 0.0f, + scale, -scale, 1.0f, 0.8f, 0.4f, 200.0f, shader ); + } + + switch( rand() & 3 ) + { + case 1: + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_BODY, cgs.media.waterDropSound1 ); + break; + case 2: + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_BODY, cgs.media.waterDropSound2 ); + break; + default: + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_BODY, cgs.media.waterDropSound3 ); + break; + } + return qtrue; + +} + +qboolean JackTheDripper( localEntity_t *le ) +{ + trace_t trace; + vec3_t vel, down = {0,0,-1}, end, origin, new_origin; + float time, dis, diameter = 1.0; + qhandle_t shader = 0; + int maxDripsPerLifetime = 200; // given a 10 second lifetime + int desiredDrips = 1 + (int)(le->data.spawner.variance * (maxDripsPerLifetime-1)); // range of (1...max) + float percentLife = 1.0f - (le->endTime - cg.time)*le->lifeRate; + localEntity_t *splash = NULL; + + switch (le->data.spawner.data1) + { + case 1: + shader = cgs.media.oilDropShader; + break; + case 2: + shader = cgs.media.greenDropShader; + break; + case 0: + default: + shader = cgs.media.waterDropShader; + break; + } + + // do we need to add a drip to maintain our drips-per-second rate? + while ( (int)(flrandom(percentLife-0.05,percentLife+0.05)*desiredDrips) > le->data.spawner.data2) + { + VectorCopy(le->refEntity.origin, origin); + + // the more drips per second, spread them out from our origin point + fxRandCircumferencePos(origin, down, 10*le->data.spawner.variance, new_origin); + + // Ideally, zero should be used for vel...so just use something sufficiently close + VectorSet( vel, 0, 0, -0.00001 ); + + // Find out where it will hit + VectorMA( new_origin, 1024, down, end ); + CG_Trace( &trace, new_origin, NULL, NULL, end, 0, MASK_SHOT ); + if ( trace.fraction < 1.0 ) + { + VectorSubtract( trace.endpos, new_origin, end ); + dis = VectorNormalize( end ); + + time = sqrt( 2*dis / DEFAULT_GRAVITY ) * 1000; // Calculate how long the thing will take to travel that distance + + // Falling drop + splash = FX_AddParticle( new_origin, vel, qtrue, diameter, 0.0, 0.8, 0.8, 0.0, 0.0, time, shader, DripCallback ); + splash->data.spawner.data1 = le->data.spawner.data1; + + splash = FX_AddSpawner(trace.endpos, trace.plane.normal, vel, NULL, qfalse, time, 0, time + 200, DripSplash, 10); + splash->data.spawner.data1 = le->data.spawner.data1; + } + else + // Falling a long way so just send one that will fall for 2 secs, but don't spawn a splash + { + FX_AddParticle( new_origin, vel, qtrue, diameter, 0.0, 0.8, 0.8, 0.0, 0.0, 2000, shader, 0/*NULL*/ ); + } + //increase our number-of-drips counter + le->data.spawner.data2++; + } + return qtrue; +} + +//------------------------------------------------------------------------------ +void CG_Drip(centity_t *cent, int killTime ) +{ + vec3_t down = {0,0,-1}; + localEntity_t *le = NULL; + + // clamp variance to [0...1] + if (cent->currentState.angles2[0] < 0) + { + cent->currentState.angles2[0] = 0; + } + else if (cent->currentState.angles2[0] > 1) + { + cent->currentState.angles2[0] = 1; + } + // cent->currentState.angles2[0] is the degree of drippiness + // cent->currentState.time2 is the type of drip (water, oil, etc.) + le = FX_AddSpawner( cent->lerpOrigin, down, NULL, NULL, qfalse, 0, + cent->currentState.angles2[0], killTime, JackTheDripper, cent->currentState.time2 ); + //init our number-of-drips counter + le->data.spawner.data2 = 0; +} + +//------------------------------------------------------------------------------ +void CG_Chunks( vec3_t origin, vec3_t dir, float scale, material_type_t type ) +{ + int i, j, k; + int numChunks; + float baseScale = 1.0f, dist, radius; + vec3_t v; + sfxHandle_t snd = 0; + localEntity_t *le; + refEntity_t *re; + + if ( type == MT_NONE ) + return; + + if ( type >= NUM_CHUNK_TYPES ) + { + CG_Printf( "^6Chunk has invalid material %d!\n", type); + type = MT_METAL; //something legal please + } + + switch( type ) + { + case MT_GLASS: + case MT_GLASS_METAL: + snd = cgs.media.glassChunkSound; + break; + + case MT_METAL: + case MT_STONE: + case MT_WOOD: + default: + snd = cgs.media.metalChunkSound; + break; + } + + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_BODY, snd ); + + numChunks = irandom( 8, 12 ); + + // LOD num chunks + VectorSubtract( cg.snap->ps.origin, origin, v ); + dist = VectorLength( v ); + + if ( dist > 512 ) + { + numChunks *= 512.0 / dist; // 1/2 at 1024, 1/4 at 2048, etc. + } + + // attempt to scale the size of the chunks based on the size of the brush + radius = baseScale + ( ( scale - 128 ) / 128 ); + + for ( i = 0; i < numChunks; i++ ) + { + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FRAGMENT; + le->endTime = cg.time + 2000; + + VectorCopy( origin, re->origin ); + + for ( j = 0; j < 3; j++ ) + { + re->origin[j] += crandom() * 12; + } + VectorCopy( re->origin, le->pos.trBase ); + + //Velocity + VectorSet( v, crandom(), crandom(), crandom() ); + VectorAdd( v, dir, v ); + VectorScale( v, flrandom( 100, 350 ), le->pos.trDelta ); + + //Angular Velocity + VectorSet( le->angles.trBase, crandom() * 360, crandom() * 360, crandom() * 360 ); + VectorSet( le->angles.trDelta, crandom() * 90, crandom() * 90, crandom() * 90 ); + + AxisCopy( axisDefault, re->axis ); + + le->data.fragment.radius = flrandom( radius * 0.7f, radius * 1.3f ); + + re->nonNormalizedAxes = qtrue; + + if ( type == MT_GLASS_METAL ) + { + if ( rand() & 1 ) + { + re->hModel = cgs.media.chunkModels[MT_METAL][irandom(0,5)]; + } + else + { + re->hModel = cgs.media.chunkModels[MT_GLASS][irandom(0,5)]; + } + } + else + { + re->hModel = cgs.media.chunkModels[type][irandom(0,5)]; + } + + le->pos.trType = TR_GRAVITY; + le->pos.trTime = cg.time; + le->angles.trType = TR_INTERPOLATE; + le->angles.trTime = cg.time; + le->bounceFactor = 0.2f + random() * 0.2f; + le->leFlags |= LEF_TUMBLE; + + re->shaderRGBA[0] = re->shaderRGBA[1] = re->shaderRGBA[2] = re->shaderRGBA[3] = 255; + + // Make sure that we have the desired start size set + for( k = 0; k < 3; k++) + { + VectorScale( le->refEntity.axis[k], le->data.fragment.radius, le->refEntity.axis[k] ); + } + } +} + +//TiM - org is where teh spray originates, and end is where the splash ends +//The fun part is corellating the height and direction to the bezier function... heh +void CG_FountainSpurt( vec3_t org, vec3_t end ) +{ + int /*i,*/ t; + vec3_t org1, org2, cpt1, cpt2; + vec3_t dir/*, dir2*/; + //vec3_t rgb = { 0.4f, 0.7f, 0.8f }; + //float distance; + //FXBezier *fxb; + localEntity_t *le; + + // offset table, could have used sin/cos, I suppose + //TiM - This was for the 4-way fountain. not needed no more + /*const float m[][2] = { + 1, 0, + 0, 1, + -1, 0, + 0, -1 };*/ + + // The origin shouldn't be in solid, otherwise the ent won't think. So, place the spawner above + // the solid object, then move the spout spawn points down to where they should be. + //org[2] -= 56; // magic number stuff + //TiM... uh O_o I think that was another thing with the 4 way fountain... won't work too well O_o + //Lessee what happens if we blatently disregard this lol + + // Create four spouts + //for ( i = 0; i < 4; i++ ) //TiM No! + //{ + //TiM - Set a direction we can use to figure out the end from the start.... >.< + //We'll need the literal direction between X + Y... Z is pretty easy to figure out + //VectorSubtract( end, org, dir ); + //distance = (dir[0] + dir[1]) * 0.5f; //so get the average + + // Move the spout out from the exact center + VectorCopy( org, org1 ); + + //TiM: No offset for now + //org1[0] += 35 * m[i][0]; + //org1[1] += 35 * m[i][1]; + + // Create our Bezier path control points + //TiM: judging from the hardcoded values, point 1 is positioned 45% units away from length, on same Z-axis + //VectorSet( cpt1, 50 * m[i][0], 50 * m[i][1], 0 ); + //VectorAdd( org1, cpt1, cpt1 ); + //VectorSet( cpt1, org1[0] + 100, org[1] + 100, 0 ); + + VectorSet( cpt1, org[0] + ( end[0] - org[0] ) * 0.65f, org[1] + ( end[1] - org[1] ) * 0.65f, org1[2] ); + //VectorAdd( org1, cpt1, cpt1 ); + + //Com_Printf("ORG = { %f, %f, %f }, CPT = { %f, %f, %f }\n", org1[0], org1[1], org1[2], cpt1[0], cpt1[1], cpt1[2] ); + + //point 2 is positioned + //TiM - point 2 I guess is the remaining 55% + //VectorSet( cpt2, 60 * m[i][0], 60 * m[i][1], -78 ); + //VectorAdd( org1, cpt2, cpt2 ); + + //VectorSet( cpt2, distance * 0.55, distance * 0.55, -Q_fabs(org1[2] - end[2]) ); + //VectorAdd( org1, cpt2, cpt2 ); + VectorCopy( end, cpt2 ); + + // Create the second endpoint--for now just try and use the last control point + VectorCopy( cpt2, org2 ); + + // Add the main spout + le = FX_AddBezier( org1, org2, cpt1, cpt2, NULL, NULL, NULL, NULL, 4, 90, + cgs.media.fountainShader); + + //if ( fxb ) + // fxb->SetSTScale( 0.7f ); + + // Add a hazy faint spout + le = FX_AddBezier( org1, org2, cpt1, cpt2, NULL, NULL, NULL, NULL, 10, 200, + cgs.media.fountainShader); + + //if ( fxb ) + // fxb->SetSTScale( 0.7f ); + + // Create misty bits at the impact point + VectorSet( dir, crandom(), crandom(), crandom() + 4 ); // always move mostly up + VectorScale( dir, random() * 3 + 2, dir ); + FX_AddSprite( org2, dir, qfalse, 20, -8, 0.3f, 0.0, 0, 0, 600, cgs.media.steamShader ); + + // ripple shader + VectorSet( dir, 0, 0, 1 ); // normal + //VectorSet( dir2, crandom() * 8, crandom() * 8, 0 ); // random drift + FX_AddQuad( org2, dir, 14.0f, 6.0f + random() * 16.0f, 0.2f, 0.0f, crandom() * 50, 800, cgs.media.rippleShader ); + + // Spray from nozzle + for ( t = 0; t < 2; t++ ) + { + //VectorSet( dir, 45 * m[i][0] + crandom() * 12, 45 * m[i][1] + crandom() * 12, crandom() * 16 ); + //VectorSet( dir2, -5 * m[i][0], -5 * m[i][1], -95 ); + VectorSet( dir, 0.4 * ( end[0] - org[0] ) + crandom() * 12, 0.4 * ( end[1] - org[1] ) + crandom() * 12, crandom() * 16 ); + //VectorSet( dir2, -0.04 * distance, -0.04 * distance, -95 ); + + FX_AddSprite( org1, dir, qtrue, 0.9f, 0.0f, 0.7f, 0.1f, 0.0f, 0.0f, 400.0f, cgs.media.waterDropShader ); + } + + // Impact splashes + for ( t = 0; t < 3; t++ ) + { + VectorCopy( org2, org1 ); + org1[0] += crandom() * 2; + org1[1] += crandom() * 2; + //VectorSet( dir, m[i][0] * 14 + crandom() * 16, m[i][1] * 14 + crandom() * 16, 50 + random() * 50 ); + VectorSet( dir, ( end[0] - org[0] ) * 0.127 + crandom() * 16, ( end[1] - org[1] ) * 0.127 + crandom() * 16, 50 + random() * 50 ); + //VectorSet( dir2, 0, 0, -250 ); + FX_AddSprite( org1, dir, qtrue, 1.1f, -0.4f, 0.7f, 0.1f, 0.0f, 0.0f, 400.0f, cgs.media.waterDropShader ); + } + //} + //Com_Printf( S_COLOR_RED "Rendering Fountain\n" ); +} + +/*================ +CG_ElectricalExplosion +=================*/ + +void smoke_puffs( vec3_t position, vec3_t dest, vec3_t dir, vec3_t user ) +{ + vec3_t direc; + + direc[0] = crandom() * 7; + direc[1] = crandom() * 7; + direc[2] = random() * 6 + 8; + + FX_AddSprite( position, direc, qfalse, 6.0f, 10.0f, 0.3f, 0.0f, 0.0f, 0.0f, 2200, cgs.media.steamShader ); +} + +//------------------------------------------------------------------------------ +void electric_spark( vec3_t pos, vec3_t normal, vec3_t dir, vec3_t user ) +{ + CG_Spark( pos, normal, 0, 10000 ); +} + +//------------------------------------------------------------------------------ +void CG_ElectricalExplosion( vec3_t start, vec3_t dir, float radius ) +{ + localEntity_t *le; + localEntity_t *particle; //FXTrail + vec3_t pos, temp/*, angles*/; + int i, numSparks; + float scale, dscale; + + // Spawn some delayed smoke + /*FX_AddSpawner( start, dir, NULL, NULL, 150, 40, qfalse, 9000, smoke_puffs ); + vectoangles( dir, angles ); + FX_AddSpawner( start, angles, NULL, NULL, 900, 800, 4000, FXF_DELAY_SPAWN, electric_spark );*/ + + // Create the sparks for the explosion + numSparks = 46 + (random() * 8.0f); + + for ( i = 0; i < numSparks; i++ ) + { + scale = 0.7f + random(); //0.2 + dscale = -scale*2; + + particle = FX_AddTrail( start, + NULL, + qfalse, + 8.0f + random() * 6, + -16.0f, + scale, + -scale, + 1.0f, + 0.0f, + 0.25f, + 700.0f, + cgs.media.spark2Shader ); + + /*if ( particle == NULL ) + return;*/ + + //FXE_Spray( dir, 200, 200, 0.3f, 500 + (rand() & 300), (FXPrimitive *) particle ); + } + + // Create some initial smoke puffs + for (i = 0; i < 12; i++) + { + VectorCopy( dir, temp ); + temp[0] += crandom() * 0.5f; + temp[1] += crandom() * 0.5f; + temp[2] += crandom() * 0.5f; + + VectorMA( start, random() * 16 + 8, temp, pos ); + VectorScale( temp, random() * 4 + 5, temp ); + + FX_AddSprite( pos, temp, qfalse, radius * 5.3f/*16.0*/, 3.0f, 1.0f, 0.0f, 0.0f, 0.0f, 2700 + random() * 600, cgs.media.steamShader ); + } + + // Now place a cool explosion model on top + VectorSubtract( cg.refdef.vieworg, start, dir ); + VectorNormalize( dir ); + + //le = CG_MakeExplosion( start, dir, cgs.media.explosionModel, 6, cgs.media.electricalExplosionSlowShader, 500, qfalse, radius * 0.01f + ( crandom() * 0.3f) ); + le = CG_MakeExplosion( start, dir, cgs.media.explosionModel, cgs.media.electricalExplosionSlowShader, 500, radius, qfalse ); + le->light = 150; + //le->refEntity. radius * 0.01f + ( crandom() * 0.3f) + + le->refEntity.renderfx |= RF_NOSHADOW; + + VectorSet( le->lightColor, 0.8f, 0.8f, 1.0f ); +} + +//RPG-X | GSIO01 | 09/05/2009: +void FX_PhaserFire2(vec3_t startpos, vec3_t endpos, vec3_t normal, qboolean impact, float scale) +{ + refEntity_t beam; + //float size; + //vec3_t velocity; + //int sparks; + vec3_t rgb = { 1,0.9,0.6}, rgb2={1,0.3,0}; + + // Draw beam first. + memset( &beam, 0, sizeof( beam ) ); + VectorCopy( startpos, beam.origin); + VectorCopy( endpos, beam.oldorigin ); + beam.reType = RT_LINE; + beam.customShader = cgs.media.phaserShader; + AxisClear( beam.axis ); + beam.shaderRGBA[0] = 0xff; + beam.shaderRGBA[1] = 0xff; + beam.shaderRGBA[2] = 0xff; + beam.shaderRGBA[3] = 0xff; + beam.data.line.width = scale + ( crandom() * 0.6f ); + beam.data.line.stscale = 5.0; + trap_R_AddRefEntityToScene( &beam ); + if (impact) + { + FX_AddQuad2( endpos, normal, (0.75f * scale) + random() * .75 + 1.0f, 0.0f, 0.5f, 0.0f, rgb, rgb2, rand() % 360, 300 + random() * 200, + cgs.media.sunnyFlareShader ); + } +} + +qboolean PhaserFX_Think(localEntity_t *le) { + vec3_t dir; + qboolean impact = qfalse; + le->data.spawner.nextthink = cg.time; + VectorSubtract(le->data.spawner.dir, le->refEntity.origin, dir); + VectorNormalize(dir); + if(le->data.spawner.data2 == 1) + impact = qtrue; + + FX_PhaserFire2(le->refEntity.origin, le->data.spawner.dir, dir, impact, le->data.spawner.data1); + + return qtrue; +} + +void CG_PhaserFX(centity_t *cent) { + localEntity_t *le; + le = FX_AddSpawner(cent->currentState.origin, cent->currentState.origin2, NULL, NULL, qfalse, 0, 0, cent->currentState.time2, PhaserFX_Think, 10); + le->data.spawner.data1 = cent->currentState.angles[0]; + le->data.spawner.data2 = cent->currentState.angles[2]; + le->data.spawner.nextthink = cg.time + (int)cent->currentState.angles[1]; +} + +qboolean TorpedoQFX_Think(localEntity_t *le) +{ + vec3_t line1end, line2end, axis[3], rgb, vel, dis; + float dist; + + VectorSubtract(le->refEntity.origin, le->addOrigin, dis); + dist = VectorLength(dis); + if(dist < 0) + dist *= -1; + if(dist < 10) + le->data.spawner.nextthink = le->endTime; + + AxisClear( axis ); + + // convert direction of travel into axis + if ( VectorNormalize2( le->data.spawner.dir, axis[0] ) == 0 ) { + axis[0][2] = 1; + } + + // spin as it moves + RotateAroundDirection( axis, cg.time * 0.3f );// * 1.25f ); + + VectorMA( le->refEntity.origin, -48.0f, axis[1], line1end ); // -24 is to high + VectorMA( le->refEntity.origin, 48.0f, axis[1], line2end ); // 24 is to high + FX_AddLine( line1end, line2end, 1.0f, random() * 18 + 2, 0.0f, 0.2 + random() * 0.2, 0.0f, 1, cgs.media.quantumGlow ); // replaced yellowParticleShader + + AxisClear( axis ); + + // convert direction of travel into axis + if ( VectorNormalize2( le->data.spawner.dir, axis[0] ) == 0 ) { + axis[0][2] = 1; + } + + // spin as it moves + RotateAroundDirection( axis, -cg.time * 0.1f );// * 1.25f ); + + VectorMA( le->refEntity.origin, 0.0f/*-128.0f*/, axis[2], line1end ); // -48 to high + VectorMA( le->refEntity.origin, 128.0f, axis[2], line2end ); // 48 to high + FX_AddLine( line1end, line2end, 1.0f, random() * 25 + 2, 0.0f, /*0.1 + random() * 0.2*/0.0f, 0.0f, 1, cgs.media.quantumGlow); + //FX_AddSprite(line1end, NULL, qfalse, random() * 90 + 30, 4, 1.0f, 0.0f, 0, 0.0f, 1.0f, cgs.media.photonStar); + + VectorSet( rgb, 1.0f, 0.45f, 0.15f ); // orange + + FX_AddSprite( le->refEntity.origin, NULL,qfalse,random() * 60 + 30, 4, 0.5f, 0.0f, 0, 0.0f, 1.0f, cgs.media.quantumRays); + //FX_AddSprite2(le->refEntity.origin, NULL,qfalse,random() * 10 + 60, 0.0f, 0.1f, 0.1f, rgb, rgb, 0.0f, 0.0f, 1, cgs.media.whiteRingShader); + FX_AddSprite( le->refEntity.origin, NULL,qfalse,random() * 40 + 8, 4, 0.5f, 0.0f, 0, 0.0f, 1.0f, cgs.media.quantumGlow ); + + VectorCopy(le->data.spawner.dir, vel); + VectorNormalize(vel); + VectorScale(vel, le->refEntity.oldorigin[0], vel); + VectorAdd(le->refEntity.origin, vel, le->refEntity.origin); + //le->data.spawner.nextthink = cg.time + 100; + + return qtrue; +} + +qboolean TorpedoPFX_Think(localEntity_t *le) +{ + vec3_t line1end, line2end, axis[3], rgb, vel, dis; + float dist; + + VectorSubtract(le->refEntity.origin, le->addOrigin, dis); + dist = VectorLength(dis); + if(dist < 0) + dist *= -1; + if(dist < 10) + le->data.spawner.nextthink = le->endTime; + + AxisClear( axis ); + + // convert direction of travel into axis + if ( VectorNormalize2( le->data.spawner.dir, axis[0] ) == 0 ) { + axis[0][2] = 1; + } + + // spin as it moves + RotateAroundDirection( axis, cg.time * 0.3f );// * 1.25f ); + + VectorMA( le->refEntity.origin, -48.0f, axis[1], line1end ); // -24 is to high + VectorMA( le->refEntity.origin, 48.0f, axis[1], line2end ); // 24 is to high + FX_AddLine( line1end, line2end, 1.0f, random() * 18 + 2, 0.0f, 0.2 + random() * 0.2, 0.0f, 1, cgs.media.photonGlow ); // replaced yellowParticleShader + + AxisClear( axis ); + + // convert direction of travel into axis + if ( VectorNormalize2( le->data.spawner.dir, axis[0] ) == 0 ) { + axis[0][2] = 1; + } + + // spin as it moves + RotateAroundDirection( axis, -cg.time * 0.1f );// * 1.25f ); + + VectorMA( le->refEntity.origin, 0.0f/*-128.0f*/, axis[2], line1end ); // -48 to high + VectorMA( le->refEntity.origin, 128.0f, axis[2], line2end ); // 48 to high + FX_AddLine( line1end, line2end, 1.0f, random() * 25 + 2, 0.0f, /*0.1 + random() * 0.2*/0.0f, 0.0f, 1, cgs.media.photonGlow); + //FX_AddSprite(line1end, NULL, qfalse, random() * 90 + 30, 4, 1.0f, 0.0f, 0, 0.0f, 1.0f, cgs.media.photonStar); + + VectorSet( rgb, 1.0f, 0.45f, 0.15f ); // orange + + FX_AddSprite( le->refEntity.origin, NULL,qfalse,random() * 60 + 30, 4, 0.5f, 0.0f, 0, 0.0f, 1.0f, cgs.media.photonRay); + //FX_AddSprite2(le->refEntity.origin, NULL,qfalse,random() * 10 + 60, 0.0f, 0.1f, 0.1f, rgb, rgb, 0.0f, 0.0f, 1, cgs.media.whiteRingShader); + FX_AddSprite( le->refEntity.origin, NULL,qfalse,random() * 40 + 8, 4, 0.5f, 0.0f, 0, 0.0f, 1.0f, cgs.media.photonGlow ); + + VectorCopy(le->data.spawner.dir, vel); + VectorNormalize(vel); + VectorScale(vel, le->refEntity.oldorigin[0], vel); + VectorAdd(le->refEntity.origin, vel, le->refEntity.origin); + //le->data.spawner.nextthink = cg.time + 100; + + return qtrue; +} + +void CG_TorpedoFX(centity_t *cent) { + localEntity_t *le; + if(cent->currentState.eventParm & 1) { // quantum fx + le = FX_AddSpawner(cent->currentState.origin, cent->currentState.angles, NULL, NULL, qfalse, 0, 0, 10000, TorpedoQFX_Think, 10); + } else { // photon fx + le = FX_AddSpawner(cent->currentState.origin, cent->currentState.angles, NULL, NULL, qfalse, 0, 0, 10000, TorpedoPFX_Think, 10); + } + le->refEntity.oldorigin[0] = cent->currentState.angles2[0]; + VectorCopy(cent->currentState.origin2, le->addOrigin); +} + +qboolean ParitcleFire_CreateParticles(localEntity_t *le) { + + return qtrue; +} + +qboolean ParticleFire_Think(localEntity_t *le) { + vec3_t velocity; + vec3_t origin; + vec3_t dir = { 0, 0 , 15 }; + float speed; + int i; + vec3_t startRGB = { 1, 0.2, 0 }; + vec3_t endRGB = { 1, 0.9, 0.7 }; + + dir[2] = Com_Clamp( 0.85f, 1.0f, dir[2] ); + + for ( i = 0; i < 3; i++ ) + { + velocity[i] = dir[i] + ( 0.2f * crandom()); + } + + VectorMA( le->refEntity.origin, 1, le->data.spawner.dir, origin); + + speed = le->data.spawner.data1 * 2.4; + + VectorScale( velocity, speed, velocity ); //speed + + FX_AddSprite2( origin, + velocity, + qfalse, //accel + le->data.spawner.data1 + (flrandom(0.4,2) * le->data.spawner.data1 ), + le->data.spawner.data1 + (crandom() * le->data.spawner.data1 * 0.5f), + 0.8, + 0.0, + startRGB, + endRGB, + 0.1f,//16.0f + random() * 45.0f, + 0.1f, + 3500, + cgs.media.fireParticle ); //flags + + le->data.spawner.nextthink = cg.time + 750; // don't generate to many or we will get low fps + + return qtrue; +} + +void CG_ParticleFire(vec3_t origin, int size) { + localEntity_t *le; + le = FX_AddSpawner(origin, NULL, NULL, NULL, qfalse, 0, 0, 10000, ParticleFire_Think, 10); + //le->data.spawner.data1 = size; +} + +qboolean ShowTrigger_Think(localEntity_t *le) { + vec4_t RGBA = { 0, 1, 0, 0.75 }; + vec3_t a, b, c, d; + + VectorCopy(le->refEntity.lightingOrigin, a); + c[0] = b[0] = le->lightColor[0]; + c[2] = d[2] = le->lightColor[2]; + b[1] = c[1] = d[1] = a[1]; + d[0] = a[0]; + b[2] = a[2]; + + FX_AddLine2(a, b, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader); + FX_AddLine2(b, c, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader); + FX_AddLine2(c, d, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader); + FX_AddLine2(d, a, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader); + + return qtrue; +} + +qboolean ShowTrigger_Think2(localEntity_t *le) { + vec4_t RGBA = { 0, 1, 0, 0.75 }; + vec3_t e, f, g, h; + + VectorCopy(le->lightColor, g); + h[0] = f[0] = le->refEntity.lightingOrigin[0]; + f[2] = e[2] = le->refEntity.lightingOrigin[2]; + e[1] = f[1] = h[1] = g[1]; + e[0] = g[0]; + h[2] = g[2]; + + FX_AddLine2(f, e, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader); + FX_AddLine2(e, g, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader); + FX_AddLine2(g, h, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader); + FX_AddLine2(h, f, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader); + + return qtrue; +} + +qboolean ShowTrigger_Think3(localEntity_t *le) { + vec4_t RGBA = { 0, 1, 0, 0.75 }; + vec3_t a, b, c, d, e, f, g, h; + + VectorCopy(le->refEntity.lightingOrigin, a); + VectorCopy(le->lightColor, g); + d[0] = f[0] = h[0] = a[0]; + b[0] = c[0] = e[0] = g[0]; + e[1] = f[1] = h[1] = g[1]; + b[1] = c[1] = d[1] = a[1]; + b[2] = f[2] = e[2] = a[2]; + h[2] = d[2] = c[2] = g[2]; + + FX_AddLine2(a, f, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader); + FX_AddLine2(d, h, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader); + FX_AddLine2(b, e, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader); + FX_AddLine2(c, g, 0.5, 0.5, 0, 0.5, 0, 1, 1, RGBA, RGBA, 10000, cgs.media.whiteShader); + + return qtrue; +} + +void CG_ShowTrigger(centity_t *cent) { + localEntity_t *le; + if(cent->currentState.eventParm) { + le = FX_AddSpawner(cent->currentState.origin, NULL, NULL, NULL, qfalse, 0, 0, 10000, ShowTrigger_Think, 0); + VectorCopy(cent->currentState.apos.trBase, le->lightColor); + VectorCopy(cent->currentState.pos.trBase, le->refEntity.lightingOrigin); + le = FX_AddSpawner(cent->currentState.origin, NULL, NULL, NULL, qfalse, 0, 0, 10000, ShowTrigger_Think2, 0); + VectorCopy(cent->currentState.apos.trBase, le->lightColor); + VectorCopy(cent->currentState.pos.trBase, le->refEntity.lightingOrigin); + le = FX_AddSpawner(cent->currentState.origin, NULL, NULL, NULL, qfalse, 0, 0, 10000, ShowTrigger_Think3, 0); + VectorCopy(cent->currentState.apos.trBase, le->lightColor); + VectorCopy(cent->currentState.pos.trBase, le->refEntity.lightingOrigin); + } else { + le = FX_AddSpawner(cent->currentState.origin, NULL, NULL, NULL, qfalse, 0, 0, 10000, ShowTrigger_Think, 0); + VectorCopy(cent->currentState.origin2, le->lightColor); + VectorCopy(cent->currentState.angles2, le->refEntity.lightingOrigin); + le = FX_AddSpawner(cent->currentState.origin, NULL, NULL, NULL, qfalse, 0, 0, 10000, ShowTrigger_Think2, 0); + VectorCopy(cent->currentState.origin2, le->lightColor); + VectorCopy(cent->currentState.angles2, le->refEntity.lightingOrigin); + le = FX_AddSpawner(cent->currentState.origin, NULL, NULL, NULL, qfalse, 0, 0, 10000, ShowTrigger_Think3, 0); + VectorCopy(cent->currentState.origin2, le->lightColor); + VectorCopy(cent->currentState.angles2, le->refEntity.lightingOrigin); + } +} + diff --git a/cgame/cg_event.c b/cgame/cg_event.c new file mode 100644 index 0000000..cc2f1ea --- /dev/null +++ b/cgame/cg_event.c @@ -0,0 +1,1792 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_event.c -- handle entity events at snapshot or playerstate transitions + +#include "cg_local.h" +#include "fx_local.h" +#include "cg_text.h" +#include "cg_screenfx.h" + +//========================================================================== + +/* +=================== +CG_PlaceString + +Also called by scoreboard drawing +=================== +*/ +const char *CG_PlaceString( int rank ) { + static char str[64]; + static char str2[64]; + char *s, *t; + + if ( rank & RANK_TIED_FLAG ) { + rank &= ~RANK_TIED_FLAG; + t = ingame_text[IGT_TIEDFOR]; + } else { + t = ""; + } + + if ( rank == 1 ) { +// s = S_COLOR_BLUE "1st" S_COLOR_WHITE; // draw in blue + Com_sprintf( str2, sizeof( str2 ), "%s%s%s",S_COLOR_BLUE, ingame_text[IGT_1ST],S_COLOR_WHITE ); + s = str2; + } else if ( rank == 2 ) { +// s = S_COLOR_RED "2nd" S_COLOR_WHITE; // draw in red + Com_sprintf( str2, sizeof( str2 ), "%s%s%s",S_COLOR_RED, ingame_text[IGT_2ND],S_COLOR_WHITE ); + s = str2; + } else if ( rank == 3 ) { +// s = S_COLOR_YELLOW "3rd" S_COLOR_WHITE; // draw in yellow + Com_sprintf( str2, sizeof( str2 ), "%s%s%s",S_COLOR_YELLOW, ingame_text[IGT_3RD],S_COLOR_WHITE ); + s = str2; + } else if ( rank == 11 ) { +// s = "11th"; + Com_sprintf( str2, sizeof( str2 ), "%s",ingame_text[IGT_11TH]); + s = str2; + } else if ( rank == 12 ) { +// s = "12th"; + Com_sprintf( str2, sizeof( str2 ), "%s",ingame_text[IGT_12TH]); + s = str2; + } else if ( rank == 13 ) { +// s = "13th"; + Com_sprintf( str2, sizeof( str2 ), "%s",ingame_text[IGT_13TH]); + s = str2; + } else if ( rank % 10 == 1 ) { +// s = va("%ist", rank); + Com_sprintf( str2, sizeof( str2 ), "%i%s",rank,ingame_text[IGT_NUM_ST]); + s = str2; + } else if ( rank % 10 == 2 ) { +// s = va("%ind", rank); + Com_sprintf( str2, sizeof( str2 ), "%i%s",rank,ingame_text[IGT_NUM_ND]); + s = str2; + } else if ( rank % 10 == 3 ) { +// s = va("%ird", rank); + Com_sprintf( str2, sizeof( str2 ), "%i%s",rank,ingame_text[IGT_NUM_RD]); + s = str2; + } else { +// s = va("%ith", rank); + Com_sprintf( str2, sizeof( str2 ), "%i%s",rank,ingame_text[IGT_NUM_TH]); + s = str2; + } + + Com_sprintf( str, sizeof( str ), "%s%s", t, s ); + return str; +} + +/* +============= +CG_Obituary +============= +*/ +static void CG_Obituary( entityState_t *ent ) { + int mod; + int target, attacker; + char *method; + const char *targetInfo; + const char *attackerInfo; + char targetName[64]; + char attackerName[64]; + + target = ent->otherEntityNum; + attacker = ent->otherEntityNum2; + mod = ent->eventParm; + + if ( target < 0 || target >= MAX_CLIENTS ) + { + CG_Error( "CG_Obituary: target out of range" ); + } + + 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( mod ) + { + case MOD_WATER: + method = ingame_text[IGT_DROWNING]; + break; + case MOD_SLIME: + method = ingame_text[IGT_CORROSION]; + break; + case MOD_LAVA: + method = ingame_text[IGT_BOILING]; + break; + case MOD_CRUSH: + method = ingame_text[IGT_COMPRESSION]; + break; + case MOD_TELEFRAG: + method = ingame_text[IGT_TRANSPORTERACCIDENT]; + break; + case MOD_FALLING: + method = ingame_text[IGT_IMPACT]; + break; + case MOD_SUICIDE: + case MOD_RESPAWN: + method = ingame_text[IGT_SUICIDE]; + break; + case MOD_TARGET_LASER: + method = ingame_text[IGT_LASERBURNS]; + break; + case MOD_TRIGGER_HURT: + method = ingame_text[IGT_MISADVENTURE]; + break; + case MOD_EXPLOSION: + method = ingame_text[IGT_DESTROYED]; + break; + case MOD_PHASER: + case MOD_PHASER_ALT: + method = ingame_text[IGT_PHASERBURNS]; + break; + case MOD_CRIFLE: + case MOD_CRIFLE_SPLASH: + case MOD_CRIFLE_ALT_SPLASH: + method = ingame_text[IGT_ENERGYSCARS]; + break; + case MOD_CRIFLE_ALT: + method = ingame_text[IGT_PHASERBURNS]; //RPG-X | GSIO01 | 08/05/2009: was IGT_SNIPED + break; + case MOD_IMOD: + case MOD_IMOD_ALT: + method = ingame_text[IGT_INFINITEMODULATION]; + break; + case MOD_SCAVENGER: + method = ingame_text[IGT_GUNNEDDOWN]; + break; + case MOD_SCAVENGER_ALT: + case MOD_SCAVENGER_ALT_SPLASH: + method = ingame_text[IGT_SCAVENGED]; + break; + case MOD_STASIS: + case MOD_STASIS_ALT: + method = ingame_text[IGT_PERMANENTSTASIS]; + break; + case MOD_GRENADE: + case MOD_GRENADE_SPLASH: + method = ingame_text[IGT_BLASTED]; + break; + case MOD_GRENADE_ALT: + case MOD_GRENADE_ALT_SPLASH: + method = ingame_text[IGT_MINED]; + break; + case MOD_TETRION: + method = ingame_text[IGT_PERFORATED]; + break; + case MOD_TETRION_ALT: + method = ingame_text[IGT_DISRUPTED]; + break; + case MOD_DREADNOUGHT: + method = ingame_text[IGT_WELDED]; + break; + case MOD_DREADNOUGHT_ALT: + method = ingame_text[IGT_DEGAUSSED]; + break; + case MOD_QUANTUM: + case MOD_QUANTUM_SPLASH: + method = ingame_text[IGT_DESTROYED]; + break; + case MOD_QUANTUM_ALT: + case MOD_QUANTUM_ALT_SPLASH: + method = ingame_text[IGT_ANNIHILATED]; + break; + case MOD_DETPACK: + method = ingame_text[IGT_VAPORIZED]; + break; + case MOD_KNOCKOUT: + method = ingame_text[IGT_KNOCKOUT]; + break; + case MOD_SEEKER: + method = ingame_text[IGT_AUTOGUNNED]; + break; + case MOD_ASSIMILATE: + method = ingame_text[IGT_ASSIMILATED]; + break; + case MOD_BORG: + case MOD_BORG_ALT: + method = ingame_text[IGT_ZAPPED]; + break; + case MOD_FORCEFIELD: + method =ingame_text[IGT_FORCEFIELDDEATH]; + break; + case MOD_FORCEDSUICIDE: + method =ingame_text[IGT_FORCEDSUICIDE]; + break; + default: + method =ingame_text[IGT_UNKNOWN]; + break; + } + + + if ( target == cg.snap->ps.clientNum ) + { + cg.mod = mod; + } + + // If killed self, send "Casualty" message. + if (attacker == target || attacker == ENTITYNUM_WORLD || !attackerInfo) + { // Killed self + if ( cg_disablekillmsgs.integer == 0 ) + { + CG_Printf(S_COLOR_CYAN"%s: "S_COLOR_WHITE"%10s "S_COLOR_CYAN"%s: "S_COLOR_WHITE"%s\n", ingame_text[IGT_CASUALTY],targetName,ingame_text[IGT_METHOD], method); + } + return; + } + + // check for kill messages from the current clientNum + if ( attacker == cg.snap->ps.clientNum ) { + char *s; + + if ( cgs.gametype < GT_TEAM ) + { + s = va("%s %s", ingame_text[IGT_YOUELIMINATED],targetName); + /*s = va("%s %s\n%s %s %i", ingame_text[IGT_YOUELIMINATED],targetName, + CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ),ingame_text[IGT_PLACEWITH], + cg.snap->ps.persistant[PERS_SCORE] );*/ + } + else + { + //Assimilated message as well + if ( mod == MOD_ASSIMILATE ) + { + s = va("%s %s", ingame_text[IGT_YOUASSIMILATED],targetName ); + } + else + { + s = va("%s %s", ingame_text[IGT_YOUELIMINATED],targetName ); + } + } + CG_CenterPrint( s, SCREEN_HEIGHT * 0.25, BIGCHAR_WIDTH ); + // print the text message as well + } + + // check for double client messages + if ( !attackerInfo ) + { + attacker = ENTITYNUM_WORLD; + strcpy( attackerName, "noname" ); + } + else + { + Q_strncpyz( attackerName, Info_ValueForKey( attackerInfo, "n" ), sizeof(attackerName) - 2); +// strcat( attackerName, S_COLOR_WHITE ); + // check for kill messages about the current clientNum + if ( target == cg.snap->ps.clientNum ) { + Q_strncpyz( cg.killerName, attackerName, sizeof( cg.killerName ) ); + } + } + + if ( cg_disablekillmsgs.integer == 0 ) + { + CG_Printf(S_COLOR_CYAN"%s: "S_COLOR_WHITE"%10s "S_COLOR_CYAN"%s: "S_COLOR_WHITE"%10s "S_COLOR_CYAN"%s: "S_COLOR_WHITE"%s\n", ingame_text[IGT_OBITELIMINATED],targetName,ingame_text[IGT_CREDIT],attackerName, ingame_text[IGT_METHOD], method); + } + +} + +//========================================================================== + +/* +=============== +CG_UseItem +=============== +*/ +static void CG_UseItem( centity_t *cent ) { + int itemNum; + entityState_t *es; + + es = ¢->currentState; + + itemNum = (es->event & ~EV_EVENT_BITS) - EV_USE_ITEM0; + if ( itemNum < 0 || itemNum > HI_NUM_HOLDABLE ) { + itemNum = 0; + } + // print a message if the local player +/* if ( es->number == cg.snap->ps.clientNum ) { + if ( !itemNum ) { + CG_CenterPrint( "No item to use", SCREEN_HEIGHT * 0.25, BIGCHAR_WIDTH ); + } else { + gitem_t *item; + item = BG_FindItemForHoldable( itemNum ); + CG_CenterPrint( va("Use %s", item->pickup_name), SCREEN_HEIGHT * 0.25, BIGCHAR_WIDTH ); + } + } +*/ + switch ( itemNum ) { + default: + case HI_NONE: + trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.useNothingSound ); + break; + + case HI_TRANSPORTER: + break; + + case HI_MEDKIT: + trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.medkitSound ); + break; + + case HI_DETPACK: + break; + + //--------------------------------------- TEMP DECOY + case HI_DECOY: + /* + if ( es->number == cg.snap->ps.clientNum ) { + CG_CenterPrint( "Decoy Placed\n",SCREEN_HEIGHT * 0.25, BIGCHAR_WIDTH ); + } + */ + break; + } + +} + +/* +================ +CG_ItemPickup + +A new item was picked up this frame +================ +*/ +static void CG_ItemPickup( int itemNum ) { + cg.itemPickup = itemNum; + cg.itemPickupTime = cg.time; + cg.itemPickupBlendTime = cg.time; + // see if it should be the grabbed weapon + + if (cg.snap->ps.stats[STAT_WEAPONS] & (1 << itemNum)) + return; + + if ( bg_itemlist[itemNum].giType == IT_WEAPON ) + { + int nCurWpn = cg.predictedPlayerState.weapon; + int nNewWpn = bg_itemlist[itemNum].giTag; + + // kef -- check cg_autoswitch... + // + // 0 == no switching + // 1 == automatically switch to best SAFE weapon + // 2 == automatically switch to best weapon, safe or otherwise + // + // NOTE: automatically switching to any weapon you pick up is stupid and annoying and we won't do it. + // + + switch(cg_autoswitch.integer) + { + case 1: + // safe switching + if ( (nNewWpn > nCurWpn) && + !(nNewWpn == WP_GRENADE_LAUNCHER) && + !(nNewWpn == WP_QUANTUM_BURST) ) + { + // switch to new wpn + cg.weaponSelectTime = cg.time; + cg.weaponSelect = nNewWpn; + } + break; + case 2: + // best, even if unsafe. + if (nNewWpn > nCurWpn) + { + // switch to new wpn + cg.weaponSelectTime = cg.time; + cg.weaponSelect = nNewWpn; + } + break; + case 3: + // Always switch + // switch to new wpn + cg.weaponSelectTime = cg.time; + cg.weaponSelect = nNewWpn; + break; + case 0: + default: + // Don't switch. + break; + } + + } +} + + +/* +================ +CG_PainEvent + +Also called by playerstate transition +================ +*/ +void CG_PainEvent( centity_t *cent, int health ) { + char *snd; + + // don't do more than two pain sounds a second + if ( cg.time - cent->pe.painTime < 500 ) { + return; + } + + if ( health < 25 ) { + snd = "*pain25.wav"; + } else if ( health < 50 ) { + snd = "*pain50.wav"; + } else if ( health < 75 ) { + snd = "*pain75.wav"; + } else { + snd = "*pain100.wav"; + } + trap_S_StartSound( NULL, cent->currentState.number, CHAN_VOICE, + CG_CustomSound( cent->currentState.number, snd ) ); + + // save pain time for programitic twitch animation + cent->pe.painTime = cg.time; + cent->pe.painDirection ^= 1; +} + +/* +============== +CG_EntityEvent + +An entity has an event value +also called by CG_CheckPlayerstateEvents +============== +*/ +#define DEBUGNAME(x) if(cg_debugEvents.integer){CG_Printf(x"\n");} +void CG_EntityEvent( centity_t *cent, vec3_t position ) { + entityState_t *es; + int event; + vec3_t dir; + const char *s; + int clientNum; + clientInfo_t *ci; + vec3_t normal = { 0, 0, 1 }; + int a, b; + + refEntity_t legs; + refEntity_t torso; + refEntity_t head; + //vec3_t forward, right, up; + //vec3_t spray_dir; //RPG-X: J2J - Trying to fix hypo, aim direction stored here as raw vector. + + es = ¢->currentState; + event = es->event & ~EV_EVENT_BITS; + + if ( cg_debugEvents.integer ) { + CG_Printf( "ent:%3i event:%3i ", es->number, event ); + } + + if ( !event ) { + DEBUGNAME("ZEROEVENT"); + return; + } + + clientNum = es->clientNum; + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + clientNum = 0; + } + ci = &cgs.clientinfo[ clientNum ]; + + switch ( event ) { + // + // movement generated events + // + case EV_FOOTSTEP: + DEBUGNAME("EV_FOOTSTEP"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + if ( ci->animSndIndex == -1 ) { + if (cg_footsteps.integer ) { + if ( cent->beamData.beamTimeParam == 0 || + ( ( es->powerups & ( 1 << PW_BEAM_OUT ) ) && ( cg.time < cent->beamData.beamTimeParam + 2200 ) ) || + ( ( es->powerups & ( 1 << PW_QUAD ) ) && ( cg.time > cent->beamData.beamTimeParam + 1800 ) ) ) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ ci->footsteps ][rand()&3] ); + } + } + } + break; + case EV_FOOTSTEP_METAL: + DEBUGNAME("EV_FOOTSTEP_METAL"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + if ( ci->animSndIndex == -1 ) { + if (cg_footsteps.integer) { + if ( cent->beamData.beamTimeParam == 0 || + ( ( es->powerups & ( 1 << PW_BEAM_OUT ) ) && ( cg.time < cent->beamData.beamTimeParam + 2200 ) ) || + ( ( es->powerups & ( 1 << PW_QUAD ) ) && ( cg.time > cent->beamData.beamTimeParam + 1800 ) ) ) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_METAL ][rand()&3] ); + } + } + } + break; + case EV_FOOTSPLASH: + DEBUGNAME("EV_FOOTSPLASH"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + if (cg_footsteps.integer) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); + } + break; + case EV_FOOTWADE: + DEBUGNAME("EV_FOOTWADE"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + if (cg_footsteps.integer) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); + } + break; + case EV_SWIM: + DEBUGNAME("EV_SWIM"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + if (cg_footsteps.integer) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); + } + break; + + + case EV_FALL_SHORT: + DEBUGNAME("EV_FALL_SHORT"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + if ( !( cent->currentState.eFlags & EF_DEAD ) ) + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound[LANDSOUND_NORMAL] ); + else + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.splatSound ); + + 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"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + if ( !( cent->currentState.eFlags & EF_DEAD ) ) + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound[LANDSOUND_NORMAL] ); + else + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.splatSound ); + + // use normal pain sound trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*pain100.wav" ) ); + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -16; + cg.landTime = cg.time; + } + break; + case EV_FALL_FAR: + DEBUGNAME("EV_FALL_FAR"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + //if ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != BOTH_FALLDEATH1INAIR && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != BOTH_FALLDEATH1LAND ) + if ( !( cent->currentState.eFlags & EF_DEAD ) ) + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound[LANDSOUND_NORMAL] ); + else + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.splatSound ); + + trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) ); + cent->pe.painTime = cg.time; // don't play a pain sound right after this + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -24; + cg.landTime = cg.time; + } + break; + + case EV_SPLAT: + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.splatSound ); + break; + case EV_STEP_4: + case EV_STEP_8: + case EV_STEP_12: + case EV_STEP_16: // smooth out step up transitions + DEBUGNAME("EV_STEP"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + { + float oldStep; + int delta; + int step; + + if ( clientNum != cg.predictedPlayerState.clientNum ) { + break; + } + // if we are interpolating, we don't need to smooth steps + if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) || + cg_nopredict.integer || cg_synchronousClients.integer ) { + break; + } + // check for stepping up before a previous step is completed + delta = cg.time - cg.stepTime; + if (delta < STEP_TIME) { + oldStep = cg.stepChange * (STEP_TIME - delta) / STEP_TIME; + } else { + oldStep = 0; + } + + // add this amount + step = 4 * (event - EV_STEP_4 + 1 ); + cg.stepChange = oldStep + step; + if ( cg.stepChange > MAX_STEP_CHANGE ) { + cg.stepChange = MAX_STEP_CHANGE; + } + cg.stepTime = cg.time; + break; + } + + case EV_JUMP_PAD: + DEBUGNAME("EV_JUMP_PAD"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + // boing sound at origin, jump sound on player + trap_S_StartSound ( cent->lerpOrigin, -1, CHAN_VOICE, cgs.media.jumpPadSound ); + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); + break; + + case EV_JUMP: + DEBUGNAME("EV_JUMP"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); + break; + case EV_TAUNT: + DEBUGNAME("EV_TAUNT"); + /*if ( ci->numTaunts > 0 ) + { + if ( ci->pClass == PC_BORG ) + { + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, CG_CustomSound( es->number, va("*taunt%d.wav", irandom(1, ci->numTaunts) ) ) ); + } + else + { + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, va("*taunt%d.wav", irandom(1, ci->numTaunts) ) ) ); + } + }*/ + break; + case EV_WATER_TOUCH: + DEBUGNAME("EV_WATER_TOUCH"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrInSound ); + break; + case EV_WATER_LEAVE: + DEBUGNAME("EV_WATER_LEAVE"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrOutSound ); + break; + case EV_WATER_UNDER: + DEBUGNAME("EV_WATER_UNDER"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrUnSound ); + break; + case EV_WATER_CLEAR: + DEBUGNAME("EV_WATER_CLEAR"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*gasp.wav" ) ); + break; + + case EV_ITEM_PICKUP: + DEBUGNAME("EV_ITEM_PICKUP"); + { + gitem_t *item; + int index; + + index = es->eventParm; // player predicted + + if ( index < 1 || index >= bg_numItems ) { + break; + } + item = &bg_itemlist[ index ]; + + // powerups and team items will have a separate global sound, this one + // will be played at prediction time + if ( item->giType == IT_POWERUP) + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.defaultPickupSound ); + } + else if (item->pickup_sound && (item->giType != IT_TEAM)) + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound ) ); + } + + // show icon and name on status bar + if ( es->number == cg.snap->ps.clientNum ) { + CG_ItemPickup( index ); + } + } + break; + + case EV_GLOBAL_ITEM_PICKUP: + DEBUGNAME("EV_GLOBAL_ITEM_PICKUP"); + { + gitem_t *item; + int index; + + index = es->eventParm; // player predicted + + if ( index < 1 || index >= bg_numItems ) { + break; + } + item = &bg_itemlist[ index ]; + + // if it's a IT_TEAM type, then its a CTF FLAG, and if so, we need to do different sounds for different people + // not nice to do as a specific sound type, but without rebuilding the item structure there is no choice. + /*if (item->giType == IT_TEAM) + { + // are we the same client as the one that fired this global sound call off in the first place? + // if so, we have already handled the sound call in EV_ITEM_PICKUP + if ( es->otherEntityNum != cg.snap->ps.clientNum ) + { + clientInfo_t *us; + + us = &cgs.clientinfo[ cg.snap->ps.clientNum ]; + + if (us->team != TEAM_SPECTATOR) + { + // red or blue? + if (item->giTag == PW_REDFLAG) + { + // red + if (us->team == TEAM_RED) + { // Your flag has been touched. + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_ANNOUNCER, cgs.media.ctfTheyStealVoiceSound); + } + else + { + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_ANNOUNCER, cgs.media.ctfYouStealVoiceSound); + } + } + else + { + // no, Blue! + if (us->team == TEAM_BLUE) + { // Your flag has been touched. + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_ANNOUNCER, cgs.media.ctfTheyStealVoiceSound); + } + else + { + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_ANNOUNCER, cgs.media.ctfYouStealVoiceSound); + } + } + } + else + { // Spectators should hear generic steal sound with no voice + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_ANNOUNCER, cgs.media.ctfStealSound); + } + } + else + { + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_ANNOUNCER, trap_S_RegisterSound( item->pickup_sound ) ); + } + + } + else*/ if (item->pickup_sound) + { // powerup pickups are NOT global anymore, they are in the world. + if (es->otherEntityNum==cg.snap->ps.clientNum) + { // YOU are the one who started this sound. + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_ITEM, trap_S_RegisterSound( item->pickup_sound ) ); + } + else + { // Maybe it is too quiet for a pickup? + trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound ) ); + } + } + + // show icon and name on status bar + if ( es->number == cg.snap->ps.clientNum ) { + CG_ItemPickup( index ); + } + } + break; + + // + // weapon events + // + case EV_NOAMMO: + DEBUGNAME("EV_NOAMMO"); +// trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.noAmmoSound ); + if ( es->number == cg.snap->ps.clientNum ) { + //primary fire switches weapons + CG_OutOfAmmoChange(qfalse); + } + break; + case EV_NOAMMO_ALT: + DEBUGNAME("EV_NOAMMO_ALT"); +// trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.noAmmoSound ); + if ( es->number == cg.snap->ps.clientNum ) { + CG_OutOfAmmoChange(qtrue); +// cg.lowAmmoWarning = 1000;//flash out of ammo message for 1 whole second +// trap_S_StartLocalSound( cgs.media.noAmmoSound, CHAN_LOCAL_SOUND ); + } + break; + case EV_CHANGE_WEAPON: + DEBUGNAME("EV_CHANGE_WEAPON"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + //trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.selectSound ); + break; + case EV_FIRE_WEAPON: + DEBUGNAME("EV_FIRE_WEAPON"); + cent->pe.empty = qfalse; + CG_FireWeapon( cent, qfalse ); + break; + case EV_ALT_FIRE: + DEBUGNAME("EV_ALT_FIRE"); + cent->pe.empty = qfalse; + CG_FireWeapon( cent, qtrue ); + break; + + case EV_USE: + DEBUGNAME("EV_USE"); + //??? + break; + + case EV_FIRE_EMPTY_PHASER: + DEBUGNAME("EV_FIRE_EMPTY_PHASER"); + cent->pe.lightningFiring = qtrue; + cent->pe.empty = qtrue; + CG_FireWeapon( cent, qfalse ); + break; + + case EV_Q_FLASH: + DEBUGNAME("EV_Q_FLASH"); + CG_InitLensFlare( cent->lerpOrigin, + 160, 500, + colorTable[CT_WHITE], 1.2, 2.0, 700, 1, + colorTable[CT_WHITE], 0, 0, 0, 0, qfalse, + 0, 0, qfalse, qfalse, + qfalse, 1.0, cg.time, 120, 0, 120 ); + break; + +case EV_HYPO_PUFF: + DEBUGNAME("EV_HYPO_PUFF"); + //UnVectorShort(cent->currentState.angles); + //VectorCopy(cent->lerpOrigin, hypo_vec); + //hypo_vec[0] += cg.snap->ps.viewheight; + //AngleVectors (cg.snap->ps.viewangles, forward, right, up); + //CalcMuzzlePoint ( cent, forward, right, up, muzzle, projsize); + //dir + + //VectorSubtract(spray_dir, /*cent->lerpOrigin*/cg.refdefViewAngles, cent->currentState.origin); + /* + spray_dir[0] = cg.refdefViewAngles[0]; + spray_dir[1] = cg.refdefViewAngles[2]; + spray_dir[2] = cg.refdefViewAngles[1]; + + //RPG-X: J2J - Correct Angles now used :-) + spray_dir[0] = cg.snap->ps.viewangles[0]; + spray_dir[1] = cg.snap->ps.viewangles[2]; + spray_dir[2] = cg.snap->ps.viewangles[1]; + */ + + FX_HypoSpray( cent->lerpOrigin, /*spray_dircg.snap->ps.viewangles*/cent->currentState.angles2, qfalse ); + break; + +case EV_TR116_TRIS: + DEBUGNAME("EV_TR116_TRIS"); + /*if(tris_state == 1){ + tris_state = 0; + }else{ + tris_state = 1; + } + + //trap_Cvar_Set("r_showtris", va("%i",tris_state)); + trap_SendConsoleCommand( va("r_showtris %i",tris_state) ); + */ + break; + +//RPG-X: RedTechie - Tring to get fricken shake sound to work +case EV_SHAKE_SOUND: + DEBUGNAME("EV_SHAKE_SOUND"); + /*spray_dir[0] = cg.refdefViewAngles[0]; + spray_dir[1] = cg.refdefViewAngles[2]; + spray_dir[2] = cg.refdefViewAngles[1]; + + FX_HypoSpray( cent->lerpOrigin, spray_dir, qfalse );*/ + //trap_S_AddLoopingSound( es->number, NULL, NULL, cgs.media.phaserEmptySound ); + trap_S_StartLocalSound( cgs.media.ShakeSound, CHAN_LOCAL ); + break; + + case EV_USE_ITEM0: + case EV_USE_ITEM1: + case EV_USE_ITEM2: + case EV_USE_ITEM3: + case EV_USE_ITEM4: + case EV_USE_ITEM5: + case EV_USE_ITEM6: + case EV_USE_ITEM7: + case EV_USE_ITEM8: + case EV_USE_ITEM9: + case EV_USE_ITEM10: + case EV_USE_ITEM11: + case EV_USE_ITEM12: + case EV_USE_ITEM13: + case EV_USE_ITEM14: + case EV_USE_ITEM15: + DEBUGNAME("EV_USE_ITEMxx"); + CG_UseItem( cent ); + break; + + //================================================================= + + // + // other events + // + case EV_PLAYER_TELEPORT_IN: + DEBUGNAME("EV_PLAYER_TELEPORT_IN"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.teleInSound ); + CG_SpawnEffect( position, &legs, &torso, &head); + CG_AddFullScreenEffect(SCREENFX_TRANSPORTER, clientNum); + break; + + case EV_PLAYER_TELEPORT_OUT: + DEBUGNAME("EV_PLAYER_TELEPORT_OUT"); + trap_S_StartSound ( NULL, es->number, CHAN_AUTO, cgs.media.qFlash ); + CG_QFlashEvent( position ); + + //trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.teleOutSound ); + //CG_SpawnEffect( position, &legs, &torso, &head); + break; + //RPG-X: TiM -> + case EV_PLAYER_TRANSPORT_IN: + DEBUGNAME("EV_PLAYER_TRANSPORT_IN"); + trap_S_StartSound ( cent->lerpOrigin, es->number, CHAN_WEAPON, cgs.media.transportSound ); + //CG_AddFullScreenEffect( SCREENFX_SP_TRANSPORTER_IN, clientNum ); + break; + case EV_PLAYER_TRANSPORT_OUT: + DEBUGNAME("EV_PLAYER_TRANSPORT_OUT"); + trap_S_StartSound ( cent->lerpOrigin, es->number, CHAN_WEAPON, cgs.media.transportSound ); + //CG_AddFullScreenEffect( SCREENFX_SP_TRANSPORTER_OUT, clientNum ); + break; +/* case EV_BORG_TELEPORT: + DEBUGNAME("EV_BORG_TELEPORT"); + FX_BorgTeleport( position ); + // FIXME: Hmmm, sound? + break;*/ + case EV_ITEM_POP: + DEBUGNAME("EV_ITEM_POP"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.respawnSound ); +//bookmark + break; + case EV_ITEM_RESPAWN: + DEBUGNAME("EV_ITEM_RESPAWN"); + cent->miscTime = cg.time; // scale up from this + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.respawnSound ); + break; + + case EV_GRENADE_BOUNCE: + DEBUGNAME("EV_GRENADE_BOUNCE"); + UnVectorShort(cent->currentState.angles2); + CG_BounceEffect( cent, es->weapon, position, cent->currentState.angles2 ); + break; + + // + // missile impacts + // + case EV_MISSILE_STICK: + DEBUGNAME("EV_MISSILE_STICK"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.grenadeAltStickSound ); + break; + + case EV_MISSILE_HIT: + DEBUGNAME("EV_MISSILE_HIT"); + ByteToDir( es->eventParm, dir ); + CG_MissileHitPlayer( cent, es->weapon, position, dir); + break; + + case EV_MISSILE_MISS: + DEBUGNAME("EV_MISSILE_MISS"); + ByteToDir( es->eventParm, dir ); + CG_MissileHitWall( cent, es->weapon, position, dir ); + break; + + case EV_COMPRESSION_RIFLE: + DEBUGNAME("EV_MISSILE_MISS"); + UnVectorShort(cent->currentState.origin2); + FX_CompressionShot( cent->lerpOrigin, cent->currentState.origin2 ); + break; + + case EV_COMPRESSION_RIFLE_ALT: + DEBUGNAME("EV_COMPRESSION_RIFLE_ALT"); + UnVectorShort(cent->currentState.origin2); + FX_CompressionAltShot( cent->lerpOrigin, cent->currentState.origin2 ); + break; + +/* case EV_IMOD: + DEBUGNAME("EV_IMOD"); + ByteToDir( es->eventParm, dir ); + FX_IMODShot( cent->lerpOrigin, cent->currentState.origin2, dir ); + break;*/ + +/* case EV_IMOD_ALTFIRE: + DEBUGNAME("EV_IMOD_ALTFIRE"); + ByteToDir( es->eventParm, dir ); + FX_AltIMODShot( cent->lerpOrigin, cent->currentState.origin2, dir ); + break;*/ + +/* case EV_IMOD_HIT: + DEBUGNAME("EV_IMOD_HIT"); + ByteToDir( es->eventParm, dir ); + FX_IMODExplosion( cent->lerpOrigin, dir ); + break;*/ + +/* case EV_IMOD_ALTFIRE_HIT: + DEBUGNAME("EV_IMOD_ALTFIRE_HIT"); + ByteToDir( es->eventParm, dir ); + FX_AltIMODExplosion( cent->lerpOrigin, dir ); + break;*/ + +/* case EV_STASIS: + DEBUGNAME("EV_STASIS"); + FX_StasisShot( cent, cent->lerpOrigin, cent->currentState.origin2 ); + break;*/ + +/* case EV_BORG_ALT_WEAPON: + DEBUGNAME("EV_BORG_ALT_WEAPON"); + FX_BorgTaser( cent->lerpOrigin, cent->currentState.origin2 ); + break;*/ + + case EV_GRENADE_EXPLODE: + DEBUGNAME("EV_GRENADE_EXPLODE"); + CG_MissileHitWall( cent, WP_GRENADE_LAUNCHER, position, normal ); + break; + + case EV_GRENADE_SHRAPNEL_EXPLODE: + DEBUGNAME("EV_GRENADE_SHRAPNEL_EXPLODE"); + ByteToDir( es->eventParm, dir ); + FX_GrenadeShrapnelExplode( position, dir ); + break; + + case EV_GRENADE_SHRAPNEL: + DEBUGNAME("EV_GRENADE_SHRAPNEL"); + // just for beeping sound + FX_GrenadeShrapnelBits(cent->lerpOrigin); + break; + + case EV_DETPACK: + DEBUGNAME("EV_DETPACK"); + FX_Detpack(cent->lerpOrigin); + break; + +/* case EV_DREADNOUGHT_MISS: + DEBUGNAME("EV_DREADNOUGHT_MISS"); + ByteToDir( es->eventParm, dir ); + FX_DreadnoughtShotMiss( cent->lerpOrigin, dir ); + break;*/ + + case EV_TETRION: + DEBUGNAME("EV_TETRION"); + // lerpOrigin == muzzle + // angles2 == firing direction + UnVectorShort(cent->currentState.angles2); + FX_TetrionShot( cent->lerpOrigin, cent->currentState.angles2 ); + break; + + //RPG-X: RedTechie - Added for fx gun + case EV_EFFECTGUN_SHOOT: + DEBUGNAME("EV_EFFECTGUN_EXPLO"); + UnVectorShort(cent->currentState.origin2); + FX_fxfunc_Shot( cent->lerpOrigin, cent->currentState.origin2 ); + //FX_fxfunc_Explosion( cent->lerpOrigin, cent->currentState.origin2 ); + break; + + case EV_SHIELD_HIT: + DEBUGNAME("EV_SHIELD_HIT"); + ByteToDir(es->eventParm, dir); + CG_PlayerShieldHit(es->otherEntityNum, dir, es->time2); + break; + + //special effects + + case EV_FX_SPARK: + DEBUGNAME("EV_FX_SPARK"); + UnVectorShort(cent->currentState.angles2); + + //CG_Printf( S_COLOR_RED "%f, %f, %f\n", cent->currentState.angles2[0], cent->currentState.angles2[1], cent->currentState.angles2[2] ); + CG_Spark( cent->lerpOrigin, cent->currentState.angles2, cent->currentState.time2, cent->currentState.time ); + break; + + case EV_FX_STEAM: + DEBUGNAME("EV_FX_STEAM"); + UnVectorShort(cent->currentState.angles2); + //CG_Printf( S_COLOR_RED "%f, %f, %f\n", cent->currentState.angles2[0], cent->currentState.angles2[1], cent->currentState.angles2[2] ); + CG_Steam( cent->lerpOrigin, cent->currentState.angles2, cent->currentState.time ); + break; + + case EV_FX_BOLT: + DEBUGNAME("EV_FX_BOLT"); + CG_Bolt( cent ); + break; + + case EV_FX_TRANSPORTER_PAD: + DEBUGNAME("EV_FX_TRANSPORTER_PAD"); + CG_TransporterPad(cent->lerpOrigin); + break; + + case EV_FX_DRIP: + DEBUGNAME("EV_FX_DRIP"); + CG_Drip(cent, cent->currentState.powerups ); + break; + + case EV_FX_CHUNKS: + DEBUGNAME("EV_FX_CHUNKS"); + UnVectorShort( cent->currentState.angles2 ); + CG_Chunks( cent->lerpOrigin, cent->currentState.angles2, cent->currentState.time2, cent->currentState.powerups ); + break; + + case EV_FX_GARDEN_FOUNTAIN_SPURT: + DEBUGNAME("EV_FX_GARDEN_FOUNTAIN_SPURT"); + CG_FountainSpurt( cent->lerpOrigin, cent->currentState.origin2 ); + break; + + case EV_FX_SURFACE_EXPLOSION: + DEBUGNAME("EV_FX_SURFACE_EXPLOSION"); + + // This is kind of cheap, but tom wanted a louder explosion...sigh. + // TiM - Jeez Tom... ;P + if ( cent->currentState.time2 & 2 ) + { + trap_S_StartSound(cent->lerpOrigin, es->number, CHAN_AUTO, cgs.media.bigSurfExpSound ); //CHAN_VOICE + } + else + { + trap_S_StartSound( cent->lerpOrigin, es->number, CHAN_AUTO, cgs.media.surfaceExpSound[irandom(0,2)] ); + } + + CG_SurfaceExplosion( cent->lerpOrigin, cent->currentState.origin2, cent->currentState.angles2[0], cent->currentState.angles2[1], + !(cent->currentState.time2 & 1) ); + break; + + case EV_FX_SMOKE: + //VectorSubtract( cent->currentState.origin2, cent->lerpOrigin, dir ); + //VectorNormalize( dir ); + UnVectorShort(cent->currentState.angles2); + //CG_Smoke( cent->lerpOrigin, dir, cent->currentState.angles2[0], 24.0f, cgs.media.smokeShader/*, 8*/ ); + CG_Smoke( cent->lerpOrigin, cent->currentState.angles2, cent->currentState.time2, cent->currentState.time ); + break; + + case EV_FX_ELECTRICAL_EXPLOSION: + DEBUGNAME("EV_FX_ELECTRICAL_EXPLOSION"); + trap_S_StartSound( cent->lerpOrigin, es->number, CHAN_AUTO, cgs.media.electricExpSound[irandom(0,2)] ); + CG_ElectricalExplosion( cent->lerpOrigin, cent->currentState.origin2, cent->currentState.angles2[0] ); + break; + + // RPG-X | Marcin | 24/12/2008 + case EV_FX_FIRE: + DEBUGNAME("EV_FX_FIRE"); + UnVectorShort(cent->currentState.angles2); + CG_Fire( cent->lerpOrigin, cent->currentState.angles2, cent->currentState.time2, cent->currentState.time, cent->currentState.eventParm ); + break; + // RPG-X | Marcin | 03/01/2009 + case EV_FX_SHAKE: + DEBUGNAME("EV_FX_SHAKE"); + CG_ExplosionEffects( cent->lerpOrigin, cent->currentState.time2, cent->currentState.time ); + break; + + case EV_SCREENFX_TRANSPORTER: + DEBUGNAME("EV_FULLSCREEN EFFECT"); + CG_AddFullScreenEffect(SCREENFX_TRANSPORTER, clientNum); + break; + + case EV_GENERAL_SOUND: + DEBUGNAME("EV_GENERAL_SOUND"); + if ( cgs.gameSounds[ es->eventParm ] ) { + trap_S_StartSound (NULL, es->number, CHAN_VOICE, cgs.gameSounds[ es->eventParm ] ); + } else { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, s ) ); + } + break; + + case EV_GLOBAL_SOUND: // play from the player's head so it never diminishes + DEBUGNAME("EV_GLOBAL_SOUND"); + if ( cgs.gameSounds[ es->eventParm ] ) { + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.gameSounds[ es->eventParm ] ); + } else { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, CG_CustomSound( es->number, s ) ); + } + break; + + /*case EV_TEAM_SOUND: // play from the player's head so it never diminishes + DEBUGNAME("EV_TEAM_SOUND"); + { + clientInfo_t *us = &cgs.clientinfo[ cg.snap->ps.clientNum ]; + + // which kind of team sound is coming through? + switch (es->eventParm) + { + case RETURN_FLAG_SOUND: + if (us->team != TEAM_SPECTATOR) + { + if (us->team == es->otherEntityNum) + { // Your flag has been returned! + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_ANNOUNCER, cgs.media.ctfYouReturnVoiceSound); + } + else + { + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_ANNOUNCER, cgs.media.ctfTheyReturnVoiceSound); + } + } + else + { // Spectators should hear a generic return sound. + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_ANNOUNCER, cgs.media.ctfReturnSound); + } + break; + + case DROPPED_FLAG_SOUND: + if (us->team != TEAM_SPECTATOR) + { + if (us->team == es->otherEntityNum) + { // Your flag was dropped by the enemy. + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_ANNOUNCER, cgs.media.ctfTheyDroppedVoiceSound); + } + else + { + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_ANNOUNCER, cgs.media.ctfYouDroppedVoiceSound); + } + } // Spectators do not hear any sound when a flag is dropped. + break; + + case SCORED_FLAG_SOUND: + if (us->team != TEAM_SPECTATOR) + { + if (us->team == es->otherEntityNum) + { // Your flag has been touched, while you are holding the enemy flag. + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_ANNOUNCER, cgs.media.ctfYouScoreVoiceSound); + } + else + { + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_ANNOUNCER, cgs.media.ctfTheyScoreVoiceSound); + } + } + else + { // Spectators should hear a generic scored sound. + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_ANNOUNCER, cgs.media.ctfScoreSound); + } + break; + + case SCORED_FLAG_NO_VOICE_SOUND: + if (us->team == es->otherEntityNum) + { + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.media.ctfScoreSound); + } + else + { // Spectators also hear this sound, a generic score sound. + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.media.ctfScoreSound); + } + break; + } + } + break;*/ + + case EV_PAIN: + // local player sounds are triggered in CG_CheckLocalSounds, + // so ignore events on the player + DEBUGNAME("EV_PAIN"); + if ( cent->currentState.number != cg.snap->ps.clientNum ) { + CG_PainEvent( cent, es->eventParm ); + } + break; + + case EV_DEATH1: + case EV_DEATH2: + case EV_DEATH3: + DEBUGNAME("EV_DEATHx"); + trap_S_StartSound( NULL, es->number, CHAN_VOICE, + CG_CustomSound( es->number, va("*death%i.wav", event - EV_DEATH1 + 1) ) ); + break; + + case EV_OBITUARY: + DEBUGNAME("EV_OBITUARY"); + CG_Obituary( es ); + break; + + case EV_DISINTEGRATION: + DEBUGNAME("EV_DISINTEGRATION"); + cg_entities[es->number].deathTime = cg.time; + FX_Disruptor( cent->lerpOrigin, 1000 ); + if (irandom(0,1)) + { + trap_S_StartSound (NULL, es->number, CHAN_VOICE, cgs.media.disintegrateSound); + } + else + { + trap_S_StartSound (NULL, es->number, CHAN_VOICE, cgs.media.disintegrate2Sound); + } + break; + + case EV_DISINTEGRATION2: + DEBUGNAME("EV_DISINTEGRATION2"); + cg_entities[es->number].deathTime = cg.time; + FX_QuantumColumns( cent->lerpOrigin ); + FX_ExplodeBits( cent->lerpOrigin); + trap_S_StartSound (NULL, es->number, CHAN_VOICE, cgs.media.playerExplodeSound); + break; + + case EV_EXPLODESHELL: + DEBUGNAME("EV_EXPLODESHELL"); + cg_entities[es->number].deathTime = cg.time; + FX_ExplodeBits( cent->lerpOrigin); + trap_S_StartSound (NULL, es->number, CHAN_VOICE, cgs.media.playerExplodeSound); + break; + + case EV_ARCWELD_DISINT: + DEBUGNAME("EV_ARCWELD_DISINT"); + cg_entities[es->number].deathTime = cg.time; + VectorSubtract( cg.refdef.vieworg, cent->lerpOrigin, dir ); + VectorNormalize( dir ); + break; + + // + // powerup events + // +/* case EV_POWERUP_BATTLESUIT: + DEBUGNAME("EV_POWERUP_BATTLESUIT"); + if ( es->number == cg.snap->ps.clientNum ) { + cg.powerupActive = PW_BOLTON; + cg.powerupTime = cg.time; + } + trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.invulnoProtectSound ); + break;*/ + /*case EV_POWERUP_REGEN: + DEBUGNAME("EV_POWERUP_REGEN"); + if ( es->number == cg.snap->ps.clientNum ) { + cg.powerupActive = PW_LASER; + cg.powerupTime = cg.time; + } + trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.regenSound ); + break;*/ + case EV_POWERUP_SEEKER_FIRE: + DEBUGNAME("EV_POWERUP_SEEKER_FIRE"); + CG_FireSeeker( cent ); + break; + case EV_DEBUG_LINE: + DEBUGNAME("EV_DEBUG_LINE"); + FX_AddLine(cent->lerpOrigin, cent->currentState.origin2, 1.0, 2.0, 0.0, 1.0, 0.0, 5000.0, (qhandle_t)0); + break; + + case EV_OBJECTIVE_COMPLETE: + if ( es->eventParm == 0 ) + {//Special code meaning clear all objectives + int i; + for ( i = 0; i < MAX_OBJECTIVES; i++ ) + { + cgs.objectives[i].complete = qfalse; + } + return; + } + if ( es->eventParm < 0 || es->eventParm > MAX_OBJECTIVES ) + {//FIXME: error message? + return; + } + cgs.objectives[es->eventParm-1].complete = qtrue; + break; + + case EV_ADAPT_SOUND: + trap_S_StartSound(NULL, es->number, CHAN_ITEM, cgs.media.invulnoProtectSound); + break; + + case EV_FX_PHASER: + s = CG_ConfigString(CS_SOUNDS + es->time); + trap_S_StartSound(NULL, es->number, CHAN_VOICE, CG_CustomSound(es->number, s)); + CG_PhaserFX(cent); + break; + + case EV_SET_CLOAK: + ci->silentCloak = es->eventParm; + break; + + case EV_FX_TORPEDO: + s = CG_ConfigString( CS_SOUNDS + es->time ); + trap_S_StartSound(NULL, es->number, CHAN_VOICE, CG_CustomSound(es->number, s)); + CG_TorpedoFX(cent); + break; + + case EV_FOOTSTEP_GRASS: + DEBUGNAME("EV_FOOTSTEP_GRASS"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + if ( ci->animSndIndex == -1 ) { + if (cg_footsteps.integer) { + if ( cent->beamData.beamTimeParam == 0 || + ( ( es->powerups & ( 1 << PW_BEAM_OUT ) ) && ( cg.time < cent->beamData.beamTimeParam + 2200 ) ) || + ( ( es->powerups & ( 1 << PW_QUAD ) ) && ( cg.time > cent->beamData.beamTimeParam + 1800 ) ) ) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_GRASS ][rand()&3] ); + } + } + } + break; + + case EV_FOOTSTEP_GRAVEL: + DEBUGNAME("EV_FOOTSTEP_GRAVEL"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + if ( ci->animSndIndex == -1 ) { + if (cg_footsteps.integer) { + if ( cent->beamData.beamTimeParam == 0 || + ( ( es->powerups & ( 1 << PW_BEAM_OUT ) ) && ( cg.time < cent->beamData.beamTimeParam + 2200 ) ) || + ( ( es->powerups & ( 1 << PW_QUAD ) ) && ( cg.time > cent->beamData.beamTimeParam + 1800 ) ) ) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_GRAVEL ][rand()&3] ); + } + } + } + break; + + case EV_FOOTSTEP_SNOW: + DEBUGNAME("EV_FOOTSTEP_SNOW"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + if ( ci->animSndIndex == -1 ) { + if (cg_footsteps.integer) { + if ( cent->beamData.beamTimeParam == 0 || + ( ( es->powerups & ( 1 << PW_BEAM_OUT ) ) && ( cg.time < cent->beamData.beamTimeParam + 2200 ) ) || + ( ( es->powerups & ( 1 << PW_QUAD ) ) && ( cg.time > cent->beamData.beamTimeParam + 1800 ) ) ) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SNOW ][rand()&3] ); + } + } + } + break; + + case EV_FOOTSTEP_WOOD: + DEBUGNAME("EV_FOOTSTEP_WOOD"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + if ( ci->animSndIndex == -1 ) { + if (cg_footsteps.integer) { + if ( cent->beamData.beamTimeParam == 0 || + ( ( es->powerups & ( 1 << PW_BEAM_OUT ) ) && ( cg.time < cent->beamData.beamTimeParam + 2200 ) ) || + ( ( es->powerups & ( 1 << PW_QUAD ) ) && ( cg.time > cent->beamData.beamTimeParam + 1800 ) ) ) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_WOOD ][rand()&3] ); + } + } + } + break; + + + case EV_FALL_SHORT_GRASS: + DEBUGNAME("EV_FALL_SHORT_GRASS"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + if ( !( cent->currentState.eFlags & EF_DEAD ) ) + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound[LANDSOUND_GRASS] ); + else + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.splatSound ); + + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -8; + cg.landTime = cg.time; + } + break; + + case EV_FALL_SHORT_GRAVEL: + DEBUGNAME("EV_FALL_SHORT_GRAVEL"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + if ( !( cent->currentState.eFlags & EF_DEAD ) ) + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound[LANDSOUND_GRAVEL] ); + else + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.splatSound ); + + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -8; + cg.landTime = cg.time; + } + break; + + case EV_FALL_SHORT_SNOW: + DEBUGNAME("EV_FALL_SHORT_SNOW"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + if ( !( cent->currentState.eFlags & EF_DEAD ) ) + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound[LANDSOUND_SNOW] ); + else + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.splatSound ); + + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -8; + cg.landTime = cg.time; + } + break; + + case EV_FALL_SHORT_WOOD: + DEBUGNAME("EV_FALL_SHORT_WOOD"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + if ( !( cent->currentState.eFlags & EF_DEAD ) ) + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound[LANDSOUND_WOOD] ); + else + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.splatSound ); + + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -8; + cg.landTime = cg.time; + } + break; + + case EV_FALL_MEDIUM_GRASS: + DEBUGNAME("EV_FALL_MEDIUM_GRASS"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + if ( !( cent->currentState.eFlags & EF_DEAD ) ) + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound[LANDSOUND_GRASS] ); + else + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.splatSound ); + + // use normal pain sound trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*pain100.wav" ) ); + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -16; + cg.landTime = cg.time; + } + break; + + case EV_FALL_MEDIUM_GRAVEL: + DEBUGNAME("EV_FALL_MEDIUM_GRAVEL"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + if ( !( cent->currentState.eFlags & EF_DEAD ) ) + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound[LANDSOUND_GRAVEL] ); + else + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.splatSound ); + + // use normal pain sound trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*pain100.wav" ) ); + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -16; + cg.landTime = cg.time; + } + break; + + case EV_FALL_MEDIUM_SNOW: + DEBUGNAME("EV_FALL_MEDIUM_SNOW"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + if ( !( cent->currentState.eFlags & EF_DEAD ) ) + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound[LANDSOUND_SNOW] ); + else + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.splatSound ); + + // use normal pain sound trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*pain100.wav" ) ); + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -16; + cg.landTime = cg.time; + } + break; + + case EV_FALL_MEDIUM_WOOD: + DEBUGNAME("EV_FALL_MEDIUM_WOOD"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + if ( !( cent->currentState.eFlags & EF_DEAD ) ) + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound[LANDSOUND_WOOD] ); + else + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.splatSound ); + + // use normal pain sound trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*pain100.wav" ) ); + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -16; + cg.landTime = cg.time; + } + break; + + case EV_FALL_FAR_GRASS: + DEBUGNAME("EV_FALL_FAR_GRASS"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + //if ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != BOTH_FALLDEATH1INAIR && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != BOTH_FALLDEATH1LAND ) + if ( !( cent->currentState.eFlags & EF_DEAD ) ) + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound[LANDSOUND_GRASS] ); + else + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.splatSound ); + + trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) ); + cent->pe.painTime = cg.time; // don't play a pain sound right after this + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -24; + cg.landTime = cg.time; + } + break; + + case EV_FALL_FAR_GRAVEL: + DEBUGNAME("EV_FALL_FAR_GRAVEL"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + //if ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != BOTH_FALLDEATH1INAIR && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != BOTH_FALLDEATH1LAND ) + if ( !( cent->currentState.eFlags & EF_DEAD ) ) + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound[LANDSOUND_GRAVEL] ); + else + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.splatSound ); + + trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) ); + cent->pe.painTime = cg.time; // don't play a pain sound right after this + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -24; + cg.landTime = cg.time; + } + break; + + case EV_FALL_FAR_SNOW: + DEBUGNAME("EV_FALL_FAR_SNOW"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + //if ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != BOTH_FALLDEATH1INAIR && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != BOTH_FALLDEATH1LAND ) + if ( !( cent->currentState.eFlags & EF_DEAD ) ) + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound[LANDSOUND_SNOW] ); + else + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.splatSound ); + + trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) ); + cent->pe.painTime = cg.time; // don't play a pain sound right after this + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -24; + cg.landTime = cg.time; + } + break; + + case EV_FALL_FAR_WOOD: + DEBUGNAME("EV_FALL_FAR_WOOD"); + if ( es->powerups & ( 1 << PW_INVIS ) ) + break; + + //if ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != BOTH_FALLDEATH1INAIR && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != BOTH_FALLDEATH1LAND ) + if ( !( cent->currentState.eFlags & EF_DEAD ) ) + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound[LANDSOUND_WOOD] ); + else + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.splatSound ); + + trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) ); + cent->pe.painTime = cg.time; // don't play a pain sound right after this + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -24; + cg.landTime = cg.time; + } + break; + + case EV_FX_PARTICLEFIRE: + DEBUGNAME("EV_FX_PARTICLEFIRE"); + CG_ParticleFire(cent->currentState.origin, cent->currentState.eventParm); + break; + + case EV_SHOOTER_SOUND: + DEBUGNAME("EV_SHOOTER_SOUND"); + CG_PlayShooterSound(cent); + break; + + case EV_TRIGGER_SHOW: + DEBUGNAME("EV_TRIGGER_SHOW"); + CG_ShowTrigger(cent); + break; + + case EV_SCRIPT_SOUND: + DEBUGNAME("EV_SCRIPT_SOUND"); + b = es->eventParm >> 8; + a = es->eventParm - (b << 8); + if ( cgs.gameSounds[ a ] ) { + trap_S_StartSound (NULL, cent->currentState.number, b, cgs.gameSounds[ a ] ); + } else { + s = CG_ConfigString( CS_SOUNDS + a ); + trap_S_StartSound (NULL, cg.snap->ps.clientNum, b, CG_CustomSound( es->number, s ) ); + } + break; + + default: + DEBUGNAME("UNKNOWN"); + CG_Error( "Unknown event: %i", event ); + break; + } + +} + + +/* +============== +CG_CheckEvents + +============== +*/ +void CG_CheckEvents( centity_t *cent ) { + // check for event-only entities + if ( cent->currentState.eType > ET_EVENTS ) { + if ( cent->previousEvent ) { + return; // already fired + } + 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/cgame/cg_info.c b/cgame/cg_info.c new file mode 100644 index 0000000..0760f35 --- /dev/null +++ b/cgame/cg_info.c @@ -0,0 +1,871 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_info.c -- display information while data is being loading + +#include "cg_local.h" +#include "cg_text.h" + +#define MAX_LOADING_PLAYER_ICONS 16 +#define MAX_LOADING_ITEM_ICONS 26 + +static qhandle_t loadingPlayerIcon; +static qhandle_t loadingItemIcon; + +void CG_LoadBar(void); + +/* +=================== +CG_DrawLoadingIcons +=================== +*/ +static void CG_DrawLoadingIcons( void ) { +// int n; + int x, y; + + trap_R_SetColor( colorTable[CT_WHITE]); + + x= 500; + y = 342; + if (loadingPlayerIcon) + { + CG_DrawPic( x, y, 64, 64, loadingPlayerIcon ); + } + else if (loadingItemIcon) + { + trap_R_SetColor(colorTable[CT_LTPURPLE1]); + CG_DrawPic( x, y, 64, 64, cgs.media.weaponbox2 ); + trap_R_SetColor( NULL); + CG_DrawPic( x, y, 64, 64, loadingItemIcon ); + } +} + + +/* +====================== +CG_LoadingString + +====================== +*/ +void CG_LoadingString( const char *s ) { + Q_strncpyz( cg.infoScreenText, s, sizeof( cg.infoScreenText ) ); + + trap_UpdateScreen(); +} + +/* +=================== +CG_LoadingItem +=================== +*/ +void CG_LoadingItem( int itemNum ) { + gitem_t *item; + + item = &bg_itemlist[itemNum]; + + if ( item->icon ) { + loadingItemIcon = trap_R_RegisterShaderNoMip( item->icon ); + } + + CG_LoadingString( item->pickup_name ); +} + +/* +=================== +CG_LoadingClient +=================== +*/ +void CG_LoadingClient( int clientNum ) { + const char *info; + char *skin; + char personality[MAX_QPATH]; + char model[MAX_QPATH]; + char iconName[MAX_QPATH]; + + info = CG_ConfigString( CS_PLAYERS + clientNum ); + + Q_strncpyz( model, Info_ValueForKey( info, "model" ), sizeof( model ) ); + skin = strchr( model, '/' ); + if ( skin ) { + //*skin++ = '\0'; + //} else { + // skin = "default"; + model[strlen(model) - strlen(skin)] = '\0'; + } + + //RPG-X: MODEL SYSTEM CHANGE + Com_sprintf( iconName, MAX_QPATH, "models/players_rpgx/%s/model_icon.jpg", model ); + + //Com_sprintf( iconName, MAX_QPATH, "models/players/%s/icon_%s.jpg", model, skin ); + + //CG_Printf( S_COLOR_RED "Loading %s\n", iconName ); + loadingPlayerIcon = trap_R_RegisterShaderNoMip( iconName ); //iconName; + + Q_strncpyz( personality, Info_ValueForKey( info, "n" ), sizeof(personality) ); + Q_CleanStr( personality ); + + if( cgs.gametype == GT_SINGLE_PLAYER ) { + trap_S_RegisterSound( va( "sound/voice/computer/misc/%s.wav", model ) ); //not exactly right since it'll miss subskins, but better than using personality + }//precache sound played in g_bot.c, PlayerIntroSound + + CG_LoadingString( personality ); + +} + + +/* +==================== +CG_DrawInformation + +Draw all the status / pacifier stuff during level loading +this overlays the ui version in ui_connect.c, UI_DrawConnectScreen +==================== +*/ +extern void CG_AddGameModNameToGameName( char *gamename ); +void CG_DrawInformation( void ) { + const char *s; + const char *info; + const char *sysInfo; + int y,x; +// int value; + qhandle_t levelshot; +// qhandle_t detail; + char buf[1024]; + int strlength,length; + + //trap_Cvar_Set ("rpg_playIntro", "1"); + + info = CG_ConfigString( CS_SERVERINFO ); + sysInfo = CG_ConfigString( CS_SYSTEMINFO ); + + s = Info_ValueForKey( info, "mapname" ); + levelshot = trap_R_RegisterShaderNoMip( va( "levelshots/%s.tga", s ) ); + if ( !levelshot ) { + levelshot = trap_R_RegisterShaderNoMip( "levelshots/unknownmap" ); + } + + cgs.widescreen.state = WIDESCREEN_NONE; + + trap_R_SetColor( colorTable[CT_BLACK] ); + CG_DrawPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, cgs.media.whiteShader ); + + cgs.widescreen.state = WIDESCREEN_CENTER; + + trap_R_SetColor( colorTable[CT_DKGREY] ); + CG_DrawPic( 11, 60, 260, 196, cgs.media.whiteShader ); + + trap_R_SetColor( NULL ); + CG_DrawPic( 13, 62, 256, 192, levelshot ); //correct aspect + +// trap_R_SetColor( colorTable[CT_LTGREY] ); +// CG_DrawPic( 418, 82, 132, 132, cgs.media.whiteShader ); + +// trap_R_SetColor( NULL ); +// CG_DrawPic( 420, 84, 128, 128, levelshot ); + + // blend a detail texture over it +// detail = trap_R_RegisterShader( "levelShotDetail" ); +// trap_R_DrawStretchPic( 0, 0, cgs.glconfig.vidWidth, cgs.glconfig.vidHeight, 0, 0, 1, 1, detail ); + + UI_DrawProportionalString( 10, 10, ingame_text[IGT_HOLODECKSIMULATION], UI_BIGFONT, colorTable[CT_LTORANGE] ); + + + strlength = UI_ProportionalStringWidth(ingame_text[IGT_HOLODECKSIMULATION],UI_BIGFONT); + length = 582 - (strlength + 6); + + trap_R_SetColor( colorTable[CT_DKORANGE]); + CG_DrawPic( 10 + strlength + 6, 11, length, 22,cgs.media.whiteShader); +// CG_DrawPic( 224, 11, 368, 22,cgs.media.whiteShader); + CG_DrawPic( 595, 11, 32, 32,cgs.media.halfroundr_22); // Right End + + trap_R_SetColor( colorTable[CT_DKPURPLE1]); + + CG_DrawPic( 274+333, 232, -32, 32,cgs.media.corner_12_18); // LR + CG_DrawPic( 274+333, 84, -32, -32,cgs.media.corner_12_18); // UR + + CG_DrawPic( 274, 60, 314, 18,cgs.media.whiteShader); // Top + CG_DrawPic( 274, 238, 314, 18,cgs.media.whiteShader); //Bottom + CG_DrawPic( 274, 75, 10, 170,cgs.media.whiteShader); // Left + CG_DrawPic( 274+321, 78, 12, 162,cgs.media.whiteShader); // Right + + CG_LoadBar(); + + // draw the icons of thiings as they are loaded + CG_DrawLoadingIcons(); + + // the first 150 rows are reserved for the client connection + // screen to write into + if ( cg.infoScreenText[0] ) { + UI_DrawProportionalString( 320, 442, va("%s ... %s", ingame_text[IGT_LOADING], cg.infoScreenText), + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_LTGOLD1]); + } else { + UI_DrawProportionalString( 320, 442, va("%s...", ingame_text[IGT_SNAPSHOT]), + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_LTGOLD1] ); + } + + // draw info string information + + y = 107; + x = 288; + // 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); + UI_DrawProportionalString( x, y, buf, UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_VLTGOLD1] ); + y += PROP_HEIGHT; + + // pure server + s = Info_ValueForKey( sysInfo, "sv_pure" ); + if ( s[0] == '1' ) { + UI_DrawProportionalString( x, y, ingame_text[IGT_PURESERVER], + UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_VLTGOLD1] ); + y += PROP_HEIGHT; + } + + // server-specific message of the day + s = CG_ConfigString( CS_MOTD ); + if ( s[0] ) { + UI_DrawProportionalString(320, y, s, + UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_VLTGOLD1] ); + y += PROP_HEIGHT; + } + + // some extra space after hostname and motd + y += 10; + } + + // map-specific message (long map name) + s = CG_ConfigString( CS_MESSAGE ); + if ( s[0] ) { + UI_DrawProportionalString( x, y, s, + UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_VLTGOLD1] ); + y += PROP_HEIGHT; + } + + // cheats warning + s = Info_ValueForKey( sysInfo, "sv_cheats" ); + if ( s[0] == '1' ) { + UI_DrawProportionalString( x, y, ingame_text[IGT_CHEATSAREENABLED], + UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_VLTGOLD1] ); + y += PROP_HEIGHT; + } + + // RPG-X | Marcin | 24/12/2008 + // privacy thing :p + // translate later + s = Info_ValueForKey( info, "rpg_respectPrivacy" ); + if ( atoi( s ) != 0 ) { + UI_DrawProportionalString( x, y, "PRIVACY MODE ^5ON", + UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_VLTGOLD1] ); + y += PROP_HEIGHT; + } else { + UI_DrawProportionalString( x, y, "PRIVACY MODE ^1OFF", + UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_VLTGOLD1] ); + y += PROP_HEIGHT; + } + + // game type + switch ( cgs.gametype ) + { + case GT_FFA: + s = ingame_text[IGT_GAME_FREEFORALL]; + break; + case GT_SINGLE_PLAYER: + s = ingame_text[IGT_GAME_SINGLEPLAYER]; + break; + case GT_TOURNAMENT: + s = ingame_text[IGT_GAME_TOURNAMENT]; + break; + case GT_TEAM: + s = ingame_text[IGT_GAME_TEAMHOLOMATCH]; + break; + case GT_CTF: + s = ingame_text[IGT_GAME_CAPTUREFLAG]; + break; + default: + s = ingame_text[IGT_GAME_UNKNOWN]; + break; + } + + { + char gamename[1024]; + + Q_strncpyz( gamename, s, sizeof(gamename) ); + + CG_AddGameModNameToGameName( gamename ); + + UI_DrawProportionalString( x, y, gamename, UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_VLTGOLD1] ); + } + + y += PROP_HEIGHT; + +/* value = atoi( Info_ValueForKey( info, "timelimit" ) ); + if ( value ) { + UI_DrawProportionalString( x, y, va( "%s %i",ingame_text[IGT_TIME_LIMIT], value ), + UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_VLTGOLD1] ); + y += PROP_HEIGHT; + } + + if (cgs.gametype != GT_CTF) { + value = atoi( Info_ValueForKey( info, "fraglimit" ) ); + if ( value ) { + UI_DrawProportionalString( x, y, va( "%s %i", ingame_text[IGT_POINT_LIMIT],value ), + UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_VLTGOLD1] ); + y += PROP_HEIGHT; + } + } + + if (cgs.gametype == GT_CTF) { + value = atoi( Info_ValueForKey( info, "capturelimit" ) ); + if ( value ) { + UI_DrawProportionalString( x, y, va( "%s %i",ingame_text[IGT_CAPTURE_LIMIT], value ), + UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_VLTGOLD1] ); + y += PROP_HEIGHT; + } + }*/ + + cgs.widescreen.state = WIDESCREEN_NONE; +} + +/* +==================== +CG_LoadBar +==================== +*/ +void CG_LoadBar(void) +{ + int x,y,pad; + + // Round LCARS buttons + y = 309; + x = 10; + pad = 22; + // First Bit (0987) + if (cg.loadLCARSStage < 1) + { + trap_R_SetColor( colorTable[CT_VDKBROWN1]); //PURPLE3]); + } + else + { + trap_R_SetColor( colorTable[CT_VLTGOLD1]); + CG_DrawPic( x + 222 - 20,y + 14, 16, 16,cgs.media.circle2); + UI_DrawProportionalString( x + 222, y + 14, ingame_text[IGT_REPLICATION_MATRIX],UI_SMALLFONT, colorTable[CT_VLTGOLD1]); + trap_R_SetColor( colorTable[CT_LTBROWN1]); //VLTPURPLE3]); + } + CG_DrawPic( x + 18, y +102, 128, 64,cgs.media.loading1); + + + if (cg.loadLCARSStage < 2) + { + trap_R_SetColor( colorTable[CT_VDKBLUE1]); + } + else + { + trap_R_SetColor( colorTable[CT_VLTGOLD1]); + CG_DrawPic( x + 222 - 20,y + 14, 16, 16,cgs.media.circle); + trap_R_SetColor( colorTable[CT_LTBLUE1]); + } + CG_DrawPic( x, y + 37, 64, 64,cgs.media.loading2); + + + if (cg.loadLCARSStage < 3) + { + trap_R_SetColor( colorTable[CT_DKGOLD1]); //VDKPURPLE1]); + } + else + { + trap_R_SetColor( colorTable[CT_VLTGOLD1]); + CG_DrawPic( x + 222 - 20,y + 14+pad, 16, 16,cgs.media.circle2); + UI_DrawProportionalString( x + 222, y + 14 + pad, ingame_text[IGT_HOLOGRAPHIC_PROJECTORS],UI_SMALLFONT, colorTable[CT_VLTGOLD1]); + trap_R_SetColor( colorTable[CT_LTGOLD1]); //LTPURPLE1]); + } + CG_DrawPic( x + 17, y, 128, 64,cgs.media.loading3); + + + if (cg.loadLCARSStage < 4) + { + trap_R_SetColor( colorTable[CT_VDKBLUE1]); //VDKPURPLE2]); + } + else + { + trap_R_SetColor( colorTable[CT_VLTGOLD1]); + CG_DrawPic( x + 222 - 20,y + 14+pad, 16, 16,cgs.media.circle); + trap_R_SetColor( colorTable[CT_LTBLUE1]); //PURPLE2]); + } + CG_DrawPic( x + 99, y, 128, 128,cgs.media.loading4); + + + if (cg.loadLCARSStage < 5) + { + trap_R_SetColor( colorTable[CT_VDKBROWN1]); //BLUE2]); + } + else + { + trap_R_SetColor( colorTable[CT_VLTGOLD1]); + CG_DrawPic( x + 222 - 20,y + 14+pad+pad, 16, 16,cgs.media.circle2); + UI_DrawProportionalString( x + 222, y + 14 + pad + pad, ingame_text[IGT_SIMULATION_DATA_BASE],UI_SMALLFONT, colorTable[CT_VLTGOLD1]); + trap_R_SetColor( colorTable[CT_LTBROWN1]); //VLTBLUE2]); + } + CG_DrawPic( x +137, y + 81, 64, 64,cgs.media.loading5); + + + if (cg.loadLCARSStage < 6) + { + trap_R_SetColor( colorTable[CT_VDKRED1]); //ORANGE]); + } + else + { + trap_R_SetColor( colorTable[CT_VLTGOLD1]); + CG_DrawPic( x + 222 - 20,y + 14+pad+pad, 16, 16,cgs.media.circle); + trap_R_SetColor( colorTable[CT_DKRED1]); //LTORANGE]); + } + CG_DrawPic( x + 45, y + 99, 128, 64,cgs.media.loading6); + + + if (cg.loadLCARSStage < 7) + { + trap_R_SetColor( colorTable[CT_VDKRED1]); //BLUE2]); + } + else + { + trap_R_SetColor( colorTable[CT_VLTGOLD1]); + CG_DrawPic( x + 222 - 20,y + 14+pad+pad+pad, 16, 16,cgs.media.circle2); + UI_DrawProportionalString( x + 222, y + 14 + pad + pad + pad, ingame_text[IGT_SAFETY_LOCKS],UI_SMALLFONT, colorTable[CT_VLTGOLD1]); + trap_R_SetColor( colorTable[CT_DKRED1]); //LTBLUE2]); + } + CG_DrawPic( x + 38, y + 24, 64, 128,cgs.media.loading7); + + if (cg.loadLCARSStage < 8) + { + trap_R_SetColor( colorTable[CT_VDKBROWN1]); //PURPLE1]); + } + else + { + trap_R_SetColor( colorTable[CT_VLTGOLD1]); + CG_DrawPic( x + 222 - 20,y + 14+pad+pad+pad, 16, 16,cgs.media.circle); + trap_R_SetColor( colorTable[CT_LTBROWN1]); //PURPLE1]); + } + CG_DrawPic( x + 78, y + 20, 128, 64,cgs.media.loading8); + + if (cg.loadLCARSStage < 9) + { + trap_R_SetColor( colorTable[CT_VDKBLUE2]); //VDKBROWN1]); + } + else + { + trap_R_SetColor( colorTable[CT_DKBLUE2]); //VLTBROWN1]); + } + CG_DrawPic( x +112, y + 66, 64, 128,cgs.media.loading9); + + + if (cg.loadLCARSStage < 9) + { + trap_R_SetColor( colorTable[CT_VDKORANGE]); //DKBLUE2]); + } + else + { + trap_R_SetColor( colorTable[CT_LTORANGE]); //LTBLUE2]); + } + CG_DrawPic( x + 62, y + 44, 128, 128,cgs.media.loadingcircle); // Center arrows + + cg.loadLCARScnt++; + if (cg.loadLCARScnt > 3) + { + cg.loadLCARScnt = 0; + } + + trap_R_SetColor( colorTable[CT_VDKBLUE1]); //DKPURPLE2]); + CG_DrawPic( x + 61, y + 43, 32, 32,cgs.media.loadingquarter); // Quad UL + CG_DrawPic( x + 135, y + 43, -32, 32,cgs.media.loadingquarter); // Quad UR + CG_DrawPic( x + 135, y +117, -32, -32,cgs.media.loadingquarter); // Quad LR + CG_DrawPic( x + 61, y +117, 32, -32,cgs.media.loadingquarter); // Quad LL + + trap_R_SetColor( colorTable[CT_LTBLUE1]); //LTPURPLE2]); + switch (cg.loadLCARScnt) + { + case 0 : + CG_DrawPic( x + 61, y + 43, 32, 32,cgs.media.loadingquarter); // Quad UL + break; + case 1 : + CG_DrawPic( x + 135, y + 43, -32, 32,cgs.media.loadingquarter); // Quad UR + break; + case 2 : + CG_DrawPic( x + 135, y +117, -32, -32,cgs.media.loadingquarter); // Quad LR + break; + case 3 : + CG_DrawPic( x + 61, y +117, 32, -32,cgs.media.loadingquarter); // Quad LL + break; + } + + UI_DrawProportionalString( x + 21, y + 150, "0987",UI_TINYFONT, colorTable[CT_BLACK]); + UI_DrawProportionalString( x + 3, y + 90, "18",UI_TINYFONT, colorTable[CT_BLACK]); + UI_DrawProportionalString( x + 24, y + 20, "7",UI_TINYFONT, colorTable[CT_BLACK]); + UI_DrawProportionalString( x + 93, y + 5, "51",UI_RIGHT|UI_TINYFONT, colorTable[CT_BLACK]); + UI_DrawProportionalString( x + 103, y + 5, "35",UI_TINYFONT, colorTable[CT_BLACK]); + UI_DrawProportionalString( x + 165, y + 83, "21",UI_TINYFONT, colorTable[CT_BLACK]); + UI_DrawProportionalString( x + 101, y + 149, "67",UI_TINYFONT, colorTable[CT_BLACK]); + UI_DrawProportionalString( x + 123, y + 36, "8",UI_TINYFONT, colorTable[CT_BLACK]); + + UI_DrawProportionalString( x + 90, y + 65, "1",UI_RIGHT|UI_TINYFONT, colorTable[CT_BLACK]); + UI_DrawProportionalString( x + 105, y + 65, "2",UI_TINYFONT, colorTable[CT_BLACK]); + UI_DrawProportionalString( x + 105, y + 87, "3",UI_TINYFONT, colorTable[CT_BLACK]); + UI_DrawProportionalString( x + 91, y + 87, "4",UI_RIGHT|UI_TINYFONT, colorTable[CT_BLACK]); + + trap_R_SetColor( colorTable[CT_DKGOLD1]); //DKBROWN1]); + y +=10; + CG_DrawPic( x + 130, y - 10 , 64, 16,cgs.media.loadingtrim); + CG_DrawPic( x + 130, y + 150, 64, -16,cgs.media.loadingtrim); + + CG_DrawPic( x + 150, y - 10, 432, 8, cgs.media.whiteShader); // Top line + CG_DrawPic( x + 150, y + 142, 432, 8, cgs.media.whiteShader); // Bottom line + CG_DrawPic( x + 583, y - 7, 16, 151, cgs.media.whiteShader); // Side line + + CG_DrawPic( x + 580, y + 1, 32, -16,cgs.media.loadingcorner); + CG_DrawPic( x + 580, y + 139, 32, 16,cgs.media.loadingcorner); + +} + +static int missionYcnt; +static int missionYpos; +static int missionInfoScreenY; + +#define OBJ_HORIZONTAL_BORDER_X 15 // Where graphic starts +#define OBJ_HORIZONTAL_BORDER_WIDTH 30 // Thickness of graphic +#define OBJ_TEXT_X_BORDER_LEFT 10 // Distance from right edge of graphic to circle graphic +#define OBJ_TEXT_X_BORDER_RIGHT 10 // Distance from right edge of text to right edge of screen +#define OBJ_CIRCLE_SIZE 16 // Size of circle graphic +#define OBJ_CIRCLE_TEXT_MARGIN 4 // Distance between circle and text +#define OBJ_TEXT_XPOS (OBJ_HORIZONTAL_BORDER_X + OBJ_HORIZONTAL_BORDER_WIDTH + OBJ_TEXT_X_BORDER_LEFT) +#define OBJ_SCREEN_HEIGHT 428 +#define OBJ_SCREEN_YMARGIN 8 +#define OBJ_SCREEN_Y2MARGIN 4 +#define OBJ_SCREEN_YBORDERTOP 20 +#define OBJ_SCREEN_YBORDERBOT 8 +#define OBJ_NORMAL_LINE_HEIGHT (PROP_HEIGHT * 1.15) +#define OBJ_ADDITIONAL_LINE_HEIGHT .20 + + + +/* +==================== +ObjectivePrint_Line +==================== +*/ +static void ObjectivePrint_Line(int strIndex,int color,centity_t *cent) +{ + char *str,*strBegin; + int y,pixelLen,charLen; + char holdText[1024], holdText2[2]; + char finalText[MAX_OBJ_LENGTH]; + int len,len_s,maxPixLength,charHeight; + +#ifndef Q3_VM + assert(cgs.objectives[strIndex].text); +#endif + str = cgs.objectives[strIndex].text; + + len = strlen(str); + len++; + Q_strncpyz(finalText,str,len); + len_s = strlen(str); + + pixelLen = UI_ProportionalStringWidth(finalText,UI_SMALLFONT); + + str = finalText; + maxPixLength = SCREEN_WIDTH - (OBJ_TEXT_XPOS + OBJ_TEXT_X_BORDER_RIGHT + OBJ_CIRCLE_SIZE + OBJ_CIRCLE_TEXT_MARGIN); + charHeight = OBJ_NORMAL_LINE_HEIGHT; + + if (missionYcnt) // Not the very first objective to be printed? + { + missionYpos += (PROP_HEIGHT * OBJ_ADDITIONAL_LINE_HEIGHT); // Add a little space between objective lines + } + + y =missionYpos + (charHeight * (missionYcnt)); + trap_R_SetColor( colorTable[color]); + + if (cgs.objectives[strIndex].complete) + { + CG_DrawPic( OBJ_TEXT_XPOS,y, OBJ_CIRCLE_SIZE, OBJ_CIRCLE_SIZE,cgs.media.circle); + } + else + { + CG_DrawPic( OBJ_TEXT_XPOS,y, OBJ_CIRCLE_SIZE, OBJ_CIRCLE_SIZE,cgs.media.circle2); + } + + if (pixelLen < maxPixLength) // One shot - small enough to print entirely on one line + { + UI_DrawProportionalString(OBJ_TEXT_XPOS + OBJ_CIRCLE_SIZE + OBJ_CIRCLE_TEXT_MARGIN, y,str, UI_SMALLFONT, colorTable[color] ); + ++missionYcnt; + } + // Text is too long, break into lines. + else + { + pixelLen = 0; + charLen = 0; + holdText2[1] = '\0'; + strBegin = str; + + while( *str ) + { + holdText2[0] = *str; + pixelLen += UI_ProportionalStringWidth(holdText2,UI_SMALLFONT); + pixelLen += 2; // For kerning + ++charLen; + + if (pixelLen > maxPixLength ) + { //Reached max length of this line + //step back until we find a space + while ((charLen) && (*str != ' ' )) + { + --str; + --charLen; + } + + if (*str==' ') + { + ++str; // To get past space + } + + Q_strncpyz( holdText, strBegin, charLen); + holdText[charLen] = '\0'; + strBegin = str; + pixelLen = 0; + charLen = 1; + + y = missionYpos + (charHeight * missionYcnt); + + UI_DrawProportionalString(OBJ_TEXT_XPOS + OBJ_CIRCLE_SIZE + OBJ_CIRCLE_TEXT_MARGIN, y, holdText, UI_SMALLFONT, colorTable[color] ); + ++missionYcnt; + } + else if (*(str+1) == '\0') + { + ++charLen; + + y = missionYpos + (charHeight * missionYcnt); + Q_strncpyz( holdText, strBegin, charLen); + UI_DrawProportionalString(OBJ_TEXT_XPOS + OBJ_CIRCLE_SIZE + OBJ_CIRCLE_TEXT_MARGIN, y, holdText, UI_SMALLFONT, colorTable[color] ); + ++missionYcnt; + break; + } + ++str; + } + } +} + +static int Objective_LineCnt(int strIndex,centity_t *cent) +{ + char *str,*strBegin; + int pixelLen,charLen; + char holdText[1024], holdText2[2]; + char finalText[MAX_OBJ_LENGTH]; + int len,len_s,maxPixLength; + int lineCnt; + +#ifndef Q3_VM + assert(cgs.objectives[strIndex].text); +#endif + str = cgs.objectives[strIndex].text; + + len = strlen(str); + len++; + Q_strncpyz(finalText,str,len); + len_s = strlen(str); + + pixelLen = UI_ProportionalStringWidth(finalText,UI_SMALLFONT); + lineCnt = 0; + + maxPixLength = SCREEN_WIDTH - (OBJ_TEXT_XPOS + OBJ_TEXT_X_BORDER_RIGHT + OBJ_CIRCLE_SIZE + OBJ_CIRCLE_TEXT_MARGIN); + + str = finalText; + + if (pixelLen < maxPixLength) // One shot - small enough to print entirely on one line + { + lineCnt = 1; + } + // Text is too long, break into lines. + else + { + pixelLen = 0; + charLen = 0; + holdText2[1] = '\0'; + strBegin = str; + + while( *str ) + { + holdText2[0] = *str; + pixelLen += UI_ProportionalStringWidth(holdText2,UI_SMALLFONT); + pixelLen += 2; // For kerning + ++charLen; + + if (pixelLen > maxPixLength ) + { //Reached max length of this line + //step back until we find a space + while ((charLen) && (*str != ' ' )) + { + --str; + --charLen; + } + + if (*str==' ') + { + ++str; // To get past space + } + + Q_strncpyz( holdText, strBegin, charLen); + holdText[charLen] = '\0'; + strBegin = str; + pixelLen = 0; + charLen = 1; + + lineCnt++; + } + else if (*(str+1) == '\0') + { + ++charLen; + lineCnt++; + break; + } + ++str; + } + } + return (lineCnt); +} + + +/* +==================== +Objectives_Draw +==================== +*/ +static void Objectives_Draw( centity_t *cent ) +{ + int objCnt,i,lineCnt,maxLines; + int total,textYCnt,length,color; + vec4_t newColor; + + objCnt=0; + for (i=0;i maxLines) // Too many lines? + { + lineCnt = maxLines; + } + + if (lineCnt==0) // Show there are no objectives + { + Q_strncpyz(cgs.objectives[0].text,ingame_text[IGT_NONETEXT],sizeof(cgs.objectives[0].text)); + } + + textYCnt = lineCnt * OBJ_NORMAL_LINE_HEIGHT; + + // For the space between objectives + textYCnt += (objCnt-1) * (OBJ_ADDITIONAL_LINE_HEIGHT * PROP_HEIGHT); + + // Calc starting Y of text + total = OBJ_SCREEN_YMARGIN + OBJ_SCREEN_YMARGIN + OBJ_SCREEN_Y2MARGIN + OBJ_SCREEN_Y2MARGIN + + OBJ_SCREEN_YBORDERTOP + OBJ_SCREEN_YBORDERBOT + textYCnt; + + if (OBJ_SCREEN_HEIGHT < total) // This should never happen (but just in case) + { + total = OBJ_SCREEN_HEIGHT; + } + + missionInfoScreenY = ((OBJ_SCREEN_HEIGHT - total) /2) + (OBJ_SCREEN_YMARGIN + OBJ_SCREEN_YBORDERTOP); + + missionYcnt = 0; + missionYpos = missionInfoScreenY; + + // Print top of frame + trap_R_SetColor( colorTable[CT_DKPURPLE3]); + CG_DrawPic( OBJ_HORIZONTAL_BORDER_X + 10, missionInfoScreenY - (OBJ_SCREEN_YMARGIN + OBJ_SCREEN_YBORDERTOP) , + SCREEN_WIDTH - (OBJ_HORIZONTAL_BORDER_X + OBJ_TEXT_X_BORDER_RIGHT + 10), OBJ_SCREEN_YBORDERTOP, cgs.media.whiteShader); // Middle column + + // Print bottom of frame + CG_DrawPic( OBJ_HORIZONTAL_BORDER_X + 10, missionInfoScreenY - OBJ_SCREEN_YMARGIN + textYCnt + (2 * OBJ_SCREEN_YMARGIN), + SCREEN_WIDTH - (OBJ_HORIZONTAL_BORDER_X + OBJ_TEXT_X_BORDER_RIGHT + 10), OBJ_SCREEN_YBORDERBOT, cgs.media.whiteShader); // Middle column + + length = (missionInfoScreenY - OBJ_SCREEN_YMARGIN + textYCnt + (2 * OBJ_SCREEN_YMARGIN)) - (missionInfoScreenY - (OBJ_SCREEN_YMARGIN + OBJ_SCREEN_YBORDERTOP)) - 15; + + // Print left hand column of frame + CG_DrawPic( OBJ_HORIZONTAL_BORDER_X, (missionInfoScreenY - (OBJ_SCREEN_YMARGIN + OBJ_SCREEN_YBORDERTOP)) + 10, + OBJ_HORIZONTAL_BORDER_WIDTH, length, cgs.media.whiteShader); // Middle column + + // Top corner + trap_R_SetColor( colorTable[CT_DKPURPLE3]); + CG_DrawPic( OBJ_HORIZONTAL_BORDER_X, + missionInfoScreenY - (OBJ_SCREEN_YMARGIN + OBJ_SCREEN_YBORDERTOP), + 32, 32, cgs.media.corner_ul_20_30); // Top corner + + // Bottom corner + CG_DrawPic( OBJ_HORIZONTAL_BORDER_X, + (missionInfoScreenY - OBJ_SCREEN_YMARGIN + textYCnt + (2 * OBJ_SCREEN_YMARGIN))-5, + 32, 32, cgs.media.corner_ll_8_30); // Bottom corner + + UI_DrawProportionalString( OBJ_HORIZONTAL_BORDER_X + 30, missionInfoScreenY - (OBJ_SCREEN_YMARGIN + OBJ_SCREEN_YBORDERTOP) + 2, ingame_text[IGT_OBJECTIVES],UI_SMALLFONT, colorTable[CT_BLACK]); + + + // Print the background + newColor[0] = colorTable[CT_BLACK][0]; + newColor[1] = colorTable[CT_BLACK][1]; + newColor[2] = colorTable[CT_BLACK][2]; + newColor[3] = 0.5; + trap_R_SetColor(newColor); + CG_DrawPic( (OBJ_TEXT_XPOS - OBJ_TEXT_X_BORDER_LEFT), missionInfoScreenY - OBJ_SCREEN_YMARGIN, SCREEN_WIDTH - ((OBJ_TEXT_XPOS - OBJ_TEXT_X_BORDER_LEFT)+OBJ_TEXT_X_BORDER_RIGHT) , textYCnt + (2 * OBJ_SCREEN_YMARGIN), cgs.media.whiteShader); + + // Print the lines + for (i=0;ips.clientNum]; + + Objectives_Draw(cent); +} + diff --git a/cgame/cg_lensflares.c b/cgame/cg_lensflares.c new file mode 100644 index 0000000..96ee0bb --- /dev/null +++ b/cgame/cg_lensflares.c @@ -0,0 +1,25 @@ +/*************************************************** +Copyright TiM - UberGames, 2005 + +cg_lensflares.c - Stores all of the functions +required to draw a dynamic lensflare ingame + +The flare is broken up and drawn in separate passes: + +Ambient Glow - Colored hazy glow (controlled by a vec4_t) +that expands around the core + +Direct Glow - Much stronger glow that surrounds the +core directly + +White Core - The actual white, focussed part of the flare + +Anamorphic Streak - Horizontal line running through +the flare (Current rage in lensflare FX) + +Lens Reflections - Circular parts that go in the opposite dir +of the flare itself +***************************************************/ + +//#include "cg_local.h" +//#include "cg_text.h" diff --git a/cgame/cg_local.h b/cgame/cg_local.h new file mode 100644 index 0000000..d31b293 --- /dev/null +++ b/cgame/cg_local.h @@ -0,0 +1,2480 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +#include "../game/q_shared.h" +#include "tr_types.h" +#include "../game/bg_public.h" +#include "cg_public.h" + +// The entire cgame module is unloaded and reloaded on each level change, +// so there is NO persistant data between levels on the client side. +// If you absolutely need something stored, it can either be kept +// by the server in the server stored userinfos, or stashed in a cvar. + +//RPG-X | Phenix | 13/02/2005 +#define N00bSoundCount 12 + +#define POWERUP_BLINKS 5 + +#define Q_FLASH_TIME 450 + +#define POWERUP_BLINK_TIME 1000 +#define FADE_TIME 200 +#define PULSE_TIME 200 +#define DAMAGE_DEFLECT_TIME 100 +#define DAMAGE_RETURN_TIME 400 +#define DAMAGE_TIME 500 +#define LAND_DEFLECT_TIME 150 +#define LAND_RETURN_TIME 300 +#define STEP_TIME 200 +#define DUCK_TIME 100 +#define PAIN_TWITCH_TIME 200 +#define WEAPON_SELECT_TIME 1400 +#define ITEM_SCALEUP_TIME 1000 + +// Zoom vars +#define ZOOM_TIME 150 +#define MAX_ZOOM_FOV 5.0f +#define ZOOM_IN_TIME 1000.0f +#define ZOOM_OUT_TIME 100.0f +#define ZOOM_START_PERCENT 0.3f + +#define ITEM_BLOB_TIME 200 +#define MUZZLE_FLASH_TIME 20 +#define SINK_TIME 1000 // time for fragments to sink into ground before going away +#define ATTACKER_HEAD_TIME 10000 +#define REWARD_TIME 3000 + +#define PULSE_SCALE 1.5 // amount to scale up the icons when activating + +#define MAX_STEP_CHANGE 32 + +#define MAX_VERTS_ON_POLY 10 +#define MAX_MARK_POLYS 256 + +#define STAT_MINUS 10 // num frame for '-' stats digit + +#define ICON_SIZE 48 +#define CHAR_WIDTH 32 +#define CHAR_HEIGHT 48 +#define TEXT_ICON_SPACE 4 + +#define TEAMCHAT_WIDTH 80 +#define TEAMCHAT_HEIGHT 8 + +// very large characters +#define GIANT_WIDTH 32 +#define GIANT_HEIGHT 48 + +#define NUM_FONT_BIG 1 +#define NUM_FONT_SMALL 2 + +#define NUM_CROSSHAIRS 12 + +#define MAX_OBJECTIVES 16 +#define MAX_OBJ_LENGTH 1024 +#define MAX_OBJ_TEXT_LENGTH (MAX_OBJECTIVES*MAX_OBJ_LENGTH) + +//beam defines used to calc player fading in beam sequence +#define PLAYER_BEAM_FADE 1500 //RPG-X: TiM - Time index for when player model fades in/out in a beam sequence +#define PLAYER_BEAM_FADETIME 1000 //Length of time the player will be fading out + +typedef enum { + FOOTSTEP_NORMAL, + FOOTSTEP_BORG, + FOOTSTEP_REAVER, + FOOTSTEP_SPECIES, + FOOTSTEP_WARBOT, + FOOTSTEP_METAL, + FOOTSTEP_SPLASH, + FOOTSTEP_BOOT, + FOOTSTEP_GRASS, + FOOTSTEP_GRAVEL, + FOOTSTEP_SNOW, + FOOTSTEP_WOOD, + + FOOTSTEP_TOTAL +} footstep_t; + +//RPG-X | GSIO01 | 20/05/2009: +typedef enum { + LANDSOUND_NORMAL, + LANDSOUND_METAL, + LANDSOUND_GRASS, + LANDSOUND_GRAVEL, + LANDSOUND_SNOW, + LANDSOUND_WOOD, + + LANDSOUND_TOTAL +} landsound_t; + +// For the holodoor intro + +#define TIME_INIT 100 +#define TIME_DOOR_START 1000 +#define TIME_DOOR_DUR (TIME_FADE_START-TIME_DOOR_START) +#define TIME_FADE_START 3000 +#define TIME_FADE_DUR (TIME_INTRO-TIME_FADE_START) + +/*============================================== +TiM - Since the majority of the body models use the same anim config, +I'm going to adapt the same logic from JKA where common animations +are stored in a global array and then whenever a new model is called, +a check is done to see we already have the anims parsed, rather than +parse them allll over again. Might shave a few seconds off of loadtime +and put them towards more precious roleplaying. :) + +Hrm, they'll also need to be parsed CG and G for the emote system to fully +work. +*/ + +typedef struct animsList_s { + char animFileRoute[MAX_QPATH]; + + animation_t animations[MAX_ANIMATIONS]; +} animsList_t; + +//TiM: Borrowed from EF SP +//Data struct for necessary dynamic animation sound effects. +//Useful for proper footstep sync and emote-based sound FX. +#define MAX_ANIM_SOUNDS 64 +#define MAX_RANDOM_ANIMSOUNDS 8 +typedef struct animsounds_s +{ + int keyFrame; //Frame to play sound on + int soundIndex[MAX_RANDOM_ANIMSOUNDS]; //sound file to play - FIXME: should be an index, handle random some other way? + int numRandomAnimSounds; //string variable min for va("...%d.wav", Q_irand(lowestVa, highestVa)) + int probability; //chance sound will play, zero value will not run this test (0 = 100% basically) +} animsounds_t; + +typedef struct animsSndList_s { + char animSndFileRoute[MAX_QPATH]; + + animsounds_t upperAnimSounds[MAX_ANIM_SOUNDS]; + animsounds_t lowerAnimSounds[MAX_ANIM_SOUNDS]; +} animsSndList_t; + +//TiM : JKA had 32, but since each potential char could have a +//dif anim list in EF, I'll allocate enough space if need be. +//Hopefully, if I've done my job, we won't actually ever exceed 2. :) +extern animsSndList_t cg_animsSndList[MAX_CLIENTS]; +extern int cg_numSndAnims; + +extern animsList_t cg_animsList[MAX_CLIENTS]; +extern int cg_numAnims; + +//Unlike EF SP, the anim and sound configs here are kept separately. +//I plan to make the animsounds config into a parameter in the model +//script file. It won't use up more resources, and it'll give modders additional +//flexibilty if they wish + +//================================================ + +//Struct to hold tricorder info strings +//typedef char tricString_t[MAX_TOKEN_CHARS]; + +/*================================================= +TiM: Rank Structs +The data in these structs are necessary for each seperate +rank in order to be loaded and displayed in the client +base. +==================================================*/ + +//CLR_GREY, //Grey is only used if there is no real class or rank. So it's default +typedef enum { + CLR_RED, + CLR_GOLD, + CLR_TEAL, + CLR_GREEN, + + MAX_COLORS +} rankColor_t; + +//Data needed for each rank entry in the TAB menu +typedef struct { + float w; + float h; + + int s1; + int t1; + + int s2; + int t2; + + int gWidth; + int gHeight; + + qhandle_t graphic; +} rankMenuData_t; + +/*Data relating to the loadage +and displayage of rank pips +on player models. I've set it up +this way so models are cached AS THEY ARE USED. +The point being to limit the number of shaders +we'll actually need cached at any given point. +That MAX_SHADERS error ticks me off... T_T +*/ +typedef struct { + char boltModelPath[MAX_QPATH]; + qhandle_t boltModel; + + char boltShaderPath[MAX_QPATH]; + qhandle_t boltShader; + + qboolean triedToLoad; + + qboolean admiralRank; +} rankModelData_t; + +//POSSIBLE FIXME: A string value for each rank may be needed at some stage... not sure +//Yeah... we need the formal name for the tricorder scanner. +//And we'll also need the consoleName so we can list the possible choices if needed. +typedef struct { + char consoleName[MAX_QPATH]; + char formalName[MAX_QPATH]; + + rankMenuData_t rankMenuData[MAX_COLORS]; + + rankModelData_t rankModelData; +} ranksData_t; + +typedef struct { + rankMenuData_t rankMenuData; + + rankModelData_t rankModelData; +} defaultRankData_t; + +#define MAX_CROSSHAIRS 15 + +//================================================= +//TiM: Importable - Custom Crosshair Data +typedef struct { + qboolean noDraw; + + vec4_t color; + + int s1; + int t1; + + int s2; + int t2; + + //qhandle_t graphic; +} crosshairsData_t; + +//================================================= + +typedef struct { + char formalName[25]; + int radarColor[3]; + int iconColor; + qboolean showRanks; + qboolean isMedic; + qboolean isBorg; +} cg_classData_t; + +// player entities need to track more information +// than any other type of entity. + +// note that not every player entity is a client entity, +// because corpses after respawn are outside the normal +// client numbering range + +// when changing animation, set animationTime to frameTime + lerping time +// The current lerp will finish out, then it will lerp to the new animation +typedef struct { + int oldFrame; + int oldFrameTime; // time when ->oldFrame was exactly on + + int frame; + int frameTime; // time when ->frame will be exactly on + + float backlerp; + + float yawAngle; + qboolean yawing; + float pitchAngle; + qboolean pitching; + + //TiM + float rollAngle; + qboolean rolling; + //eTiM + + int animationNumber; // may include ANIM_TOGGLEBIT + animation_t *animation; + int animationTime; // time when the first frame of the animation will be exact +} lerpFrame_t; + + +typedef struct { + lerpFrame_t head, legs, torso; + int painTime; + int painDirection; // flip from 0 to 1 + qboolean empty; + int lightningFiring; + + // machinegun spinning + float barrelAngle; + int barrelTime; + qboolean barrelSpinning; +} playerEntity_t; + +//================================================= + +//TiM - struct containing all the necessary data required +//for the SP style transporter. Due to really whacky design on id's behalf, +//this data has to be global enough for players AND weapons +//to accept it. :P +typedef struct { + //RPG-X | TiM - Time when a transport powerup is detected. + //I originally only had one variable, but found trying to track two + //different powerups with one value was causing some undesired render effects. + //This method is far better in the fact it means much cleaner render effects, + //and also if there is a delay in the server-side teleport function, the visual FX + //on this side will still remain in sync. :) + //int beamTime; + qboolean beamInDetected; + float beamAlpha; //put in here so it's in scope for me to pass it to the weapons renderer + int beamTimeParam; +} beamData_t; + +//================================================= + + +// centity_t have a direct corespondence with gentity_t in the game, but +// only the entityState_t is directly communicated to the cgame +typedef struct centity_s { + entityState_t currentState; // from cg.frame + entityState_t nextState; // from cg.nextFrame, if available + qboolean interpolate; // true if next is valid to interpolate to + qboolean currentValid; // true if cg.frame holds this entity + + int muzzleFlashTime; // move to playerEntity? + int previousEvent; + int thinkFlag; + + int trailTime; // so missile trails can handle dropped initial packets + int miscTime; + + vec3_t damageAngles; + int damageTime; + int deathTime; + + playerEntity_t pe; + + beamData_t beamData; + + int cloakTime; //time index we started cloaking, for the Q-Flash + int decloakTime; + qboolean wasCloaked; + + // Unused + int errorTime; // decay the error from this time +// vec3_t errorOrigin; +// vec3_t errorAngles; + + qboolean extrapolated; // false if origin / angles is an interpolation + vec3_t rawOrigin; + vec3_t rawAngles; + + vec3_t beamEnd; + + // exact interpolated position of entity on this frame + vec3_t lerpOrigin; + vec3_t lerpAngles; + + int turboTime; + //qboolean unclampAngles; //TiM - Unstor player clamp angles + qboolean clampAngles; +} centity_t; + +//TiM - stores the time index of each client to be used when they enter a turbolift. +//it's been placed here to ensure no changes will be made to it, unlike the cent structs +extern int cg_liftEnts[MAX_CLIENTS]; + +//====================================================================== + +// local entities are created as a result of events or predicted actions, +// and live independantly from all server transmitted entities + +typedef struct markPoly_s { + struct markPoly_s *prevMark, *nextMark; + int time; + qhandle_t markShader; + qboolean alphaFade; // fade alpha instead of rgb + float color[4]; + poly_t poly; + polyVert_t verts[MAX_VERTS_ON_POLY]; +} markPoly_t; + + +typedef enum { + LE_MARK, + LE_EXPLOSION, + LE_SPRITE_EXPLOSION, + LE_MOVE_SCALE_FADE, + LE_FALL_SCALE_FADE, + LE_FADE_RGB, + LE_SCALE_FADE, + LE_SCALE_FADE_SPRITE, + LE_SCALE_SINE, //RPG-X: TiM + LE_LINE, + LE_LINE2, + LE_OLINE, + LE_TRAIL, + LE_VIEWSPRITE, + LE_BEZIER, + LE_QUAD, + LE_CYLINDER, + LE_ELECTRICITY, + LE_PARTICLE, + LE_SPAWNER, + LE_FRAGMENT +} leType_t; + +typedef enum { + LEF_NONE = 0x0000, // Use for "no flag" + LEF_PUFF_DONT_SCALE = 0x0001, // do not scale size over time + LEF_TUMBLE = 0x0002, // tumble over time, used for ejecting shells + LEF_MOVE = 0x0004, // Sprites and models and trails use this when they use velocity + LEF_USE_COLLISION = 0x0008, // Sprites, models and trails use this when they want to collide and bounce. + LEF_ONE_FRAME = 0x0010, // One frame only is to be shown of the entity. + LEF_ONE_FRAME_DONE = 0x0020, // This bit is set AFTER the one frame is shown. + LEF_FADE_RGB = 0x0040, // Force fading through color. + LEF_SINE_SCALE = 0x0080, // TiM-Used for non-linear ratio calcs + LEF_REVERSE_SCALE = 0x0100 // TiM-Make the scale decrease instead of increase +} leFlag_t; + +typedef enum { + LEMT_NONE, +} leMarkType_t; // fragment local entities can leave marks on walls + +typedef struct localEntity_s { + struct localEntity_s *prev, *next; + leType_t leType; + int leFlags; + + int startTime; + int endTime; + + float lifeRate; // 1.0 / (endTime - startTime) + + trajectory_t pos; + trajectory_t angles; + + float bounceFactor; // 0.0 = no bounce, 1.0 = perfect + + float color[4]; + + float frameRate; + int numFrames; + +// float radius; +// float dradius; + +// float length; +// float dlength; + + float alpha; + float dalpha; + + float shaderRate; + int numShaders; + qhandle_t *leShaders; + + float light; + vec3_t lightColor; + + leMarkType_t leMarkType; + + union { + struct { + float radius; + float dradius; + vec3_t startRGB; + vec3_t dRGB; + } sprite; + struct { + float width; + float dwidth; + float length; + float dlength; + vec3_t startRGB; + vec3_t dRGB; + } trail; + struct { + float width; + float dwidth; + // Below are bezier specific. + vec3_t control1; // initial position of control points + vec3_t control2; + vec3_t control1_velocity; // initial velocity of control points + vec3_t control2_velocity; + vec3_t control1_acceleration; // constant acceleration of control points + vec3_t control2_acceleration; + } line; + struct { + float width; + float dwidth; + float width2; + float dwidth2; + vec3_t startRGB; + vec3_t dRGB; + } line2; + struct { + float width; + float dwidth; + float width2; + float dwidth2; + float height; + float dheight; + } cylinder; + struct { + float width; + float dwidth; + } electricity; + struct + { + // fight the power! open and close brackets in the same column! + float radius; + float dradius; + qboolean (*thinkFn)(struct localEntity_s *le); + vec3_t dir; // magnitude is 1, but this is oldpos - newpos right before the + //particle is sent to the renderer + // may want to add something like particle::localEntity_s *le (for the particle's think fn) + } particle; + struct + { + qboolean dontDie; + vec3_t dir; + float variance; + int delay; + int nextthink; + qboolean (*thinkFn)(struct localEntity_s *le); + int data1; + int data2; + } spawner; + struct + { + float radius; + } fragment; + } data; + + refEntity_t refEntity; + + vec3_t addOrigin; +} localEntity_t; + +// kef -- there may well be a cleaner way of doing this, but the think functions for particles +//will need access to these CG_AddXXXXXX functions, so... +void CG_AddMoveScaleFade( localEntity_t *le ); +void CG_AddScaleFade( localEntity_t *le ); +void CG_AddFallScaleFade( localEntity_t *le ); +void CG_AddExplosion( localEntity_t *ex ); +void CG_AddSpriteExplosion( localEntity_t *le ); +void CG_AddScaleFadeSprite( localEntity_t *le ); +void CG_AddQuad( localEntity_t *le ); +void CG_AddLine( localEntity_t *le ); +void CG_AddOLine( localEntity_t *le ); +void CG_AddLine2( localEntity_t *le ); +void CG_AddTrail( localEntity_t *le ); +void CG_AddViewSprite( localEntity_t *le ); +void CG_AddBezier( localEntity_t *le ); +void CG_AddCylinder( localEntity_t *le ); +void CG_AddElectricity( localEntity_t *le ); +// IMPORTANT!! Don't make CG_AddParticle or CG_AddSpawner available here to prevent unpalateable recursion possibilities +// + +//====================================================================== + + +typedef struct { + int client; + int score; + int ping; + int time; + int scoreFlags; +// int faveTarget; +// int faveTargetKills; // # of times you killed fave target + int worstEnemy; + int worstEnemyKills; // # of times worst Enemy Killed you + int faveWeapon; // your favorite weapon + int killedCnt; // # of times you were killed +} score_t; + +//********************************************************** +//RPG-X Player Model Additional Data +//This is the struct where all the data loaded from .model files +//is parsed to. From there, it can be plugged straight into +//the original model loading pipeline.... I think +//********************************************************** +#define MAX_BOLTONS 10 +#define MAX_TALK_SKINS 4 + +typedef enum { + BOLTON_HEAD = 0, + BOLTON_TORSO, + BOLTON_LEGS, + BOLTON_MAX +} boltonLoc_t; + +//min and max value for any timed events +typedef struct { + int nextTime; + + int minSeconds; + int maxSeconds; +} charSecs_t; + +typedef struct { + int modelBase; + char tagName[MAX_QPATH]; + qhandle_t tagModel; +} boltonTags_t; + + +// each client has an associated clientInfo_t +// that contains media references necessary to present the +// client model and other color coded effects +// this is regenerated each time a client's configstring changes, +// usually as a result of a userinfo (name, model, etc) change +#define MAX_CUSTOM_SOUNDS 32 +typedef struct { + qboolean infoValid; + + char name[MAX_QPATH]; + team_t team; + pclass_t pClass; + + int botSkill; // 0 = not bot, 1-5 = bot + + vec3_t color; + + int score; // updated by score servercmds + int location; // location index for team mode + int health; // you only get this info about your teammates + int armor; + int curWeapon; + + int handicap; + int wins, losses; // in tourney mode + + int powerups; // so can display quad/flag status + qboolean eliminated; // so can display quad/flag status + + qboolean hasRanks; //displays ranks on model or not + + // when clientinfo is changed, the loading of models/skins/sounds + // can be deferred until you are dead, to prevent hitches in + // gameplay + char charName[MAX_QPATH]; + char modelName[MAX_QPATH]; + char skinName[MAX_QPATH]; + qboolean deferred; + + vec3_t headOffset; // move head in icon views + footstep_t footsteps; + gender_t gender; // from model + char soundPath[MAX_QPATH]; //from model + + qhandle_t legsModel; + qhandle_t legsSkin; + + qhandle_t torsoModel; + qhandle_t torsoSkin; + + qhandle_t headModel; + qhandle_t headSkin; + + //int numBoltOns; + boltonTags_t boltonTags[MAX_BOLTONS]; + + //TiM - Additional Parameters + qhandle_t headSkinBlink; + + //frowning + qhandle_t headSkinFrown; + qhandle_t headSkinFrownBlink; + + charSecs_t headBlinkTime; + //charSecs_t headFrownTime; + + //TiM + qhandle_t headSkinTalk[MAX_TALK_SKINS]; + int nextTalkTime; + int currentTalkSkin; + + int nextTalkAngle; //Time index until the new talk angles are calculated + int talkDifferential; //Length of time between changes, without cg.time added + vec3_t talkAngles; //decimal values to offset the head orientation + int headDebounceTime; //Time to spend checking for talking checks + + qhandle_t holsterModel; //RPG-X : TiM + //-- + + //char race[256]; + + qhandle_t modelIcon; + + //TiM - Whoa... this thing is pretty huge now thanks to the 50+ anims I added :/ + //animation_t animations[MAX_ANIMATIONS]; + //animation_t *animations; + int animIndex; //index to the array cell with the anim data we want in it. :) + int animSndIndex; //index to the array cell where the sound data for certain anims is kept. + + sfxHandle_t sounds[MAX_CUSTOM_SOUNDS]; + int numTaunts; + + qboolean animsFlushed; + + //Player Model System Custom Parameters + //TiM + char age[MAX_NAME_LENGTH]; + float height; + float weight; + char race[MAX_NAME_LENGTH]; + //-- + + int modelOffset; + + qboolean isAdmin; //local store to determine if client is an admin or not (for both class and login) + + qboolean isHazardModel; //yeh lame lol + + int silentCloak; +} clientInfo_t; + + +// each WP_* weapon enum has an associated weaponInfo_t +// that contains media references necessary to present the +// weapon and its effects +typedef struct weaponInfo_s { + qboolean registered; + gitem_t *item; + + qhandle_t handsModel; // the hands don't actually draw, they just position the weapon + qhandle_t weaponModel; // this is the pickup model + qhandle_t viewModel; // this is the in-view model used by the player + qhandle_t barrelModel[4]; // Trek weapons have lots of barrels + qhandle_t flashModel; + + vec3_t weaponMidpoint; // so it will rotate centered instead of by tag + + vec3_t flashDlightColor; + + sfxHandle_t flashSound; // fast firing weapons randomly choose + sfxHandle_t altFlashSnd; + sfxHandle_t stopSound; + sfxHandle_t altStopSound; + sfxHandle_t firingSound; + sfxHandle_t altFiringSound; +// sfxHandle_t missileSound; + sfxHandle_t alt_missileSound; + sfxHandle_t mainHitSound; + sfxHandle_t altHitSound; + + qhandle_t weaponIcon; + +// qhandle_t ammoModel; + + qhandle_t missileModel; + void (*missileTrailFunc)( centity_t *, const struct weaponInfo_s *wi ); + float missileDlight; + vec3_t missileDlightColor; + + qhandle_t alt_missileModel; + void (*alt_missileTrailFunc)( centity_t *, const struct weaponInfo_s *wi ); + float alt_missileDlight; + + qboolean isAnimSndBased; //TiM - play this sound, only if the player has no anim sound cfg. +} weaponInfo_t; + + +// each IT_* item has an associated itemInfo_t +// that constains media references necessary to present the +// item and its effects +typedef struct { + qboolean registered; + qhandle_t model; + qhandle_t icon; +} itemInfo_t; + + +typedef struct { + int itemNum; +} powerupInfo_t; + +typedef struct { + char text[MAX_OBJ_LENGTH]; + char abridgedText[MAX_OBJ_LENGTH]; + qboolean complete; +} objective_t; + +//TiM - Data needed for FPS Body CVAR +typedef struct { + int anim; + int offset; + float sizeOffset; +} fpsBody_t; + +//====================================================================== + +// all cg.stepTime, cg.duckTime, cg.landTime, etc are set to cg.time when the action +// occurs, and they will have visible effects for #define STEP_TIME or whatever msec after + +typedef struct { + int clientFrame; // incremented each frame + + qboolean demoPlayback; + qboolean levelShot; // taking a level menu screenshot + int deferredPlayerLoading; + qboolean loading; // don't defer players at initial startup + qboolean intermissionStarted; // don't play voice rewards, because game will end shortly + + // there are only one or two snapshot_t that are relevent at a time + int latestSnapshotNum; // the number of snapshots the client system has received + int latestSnapshotTime; // the time from latestSnapshotNum, so we don't need to read the snapshot yet + + snapshot_t *snap; // cg.snap->serverTime <= cg.time + snapshot_t *nextSnap; // cg.nextSnap->serverTime > cg.time, or NULL + snapshot_t activeSnapshots[2]; + + float frameInterpolation; // (float)( cg.time - cg.frame->serverTime ) / (cg.nextFrame->serverTime - cg.frame->serverTime) + + qboolean thisFrameTeleport; + qboolean nextFrameTeleport; + + int frametime; // cg.time - cg.oldTime + + int time; // this is the time value that the client + // is rendering at. + int oldTime; // time at last frame, used for missile trails and prediction checking + + int physicsTime; // either cg.snap->time or cg.nextSnap->time + + int timelimitWarnings; // 5 min, 1 min, overtime + int fraglimitWarnings; + + qboolean renderingThirdPerson; // during deaths, chasecams, etc + + // prediction state + qboolean hyperspace; // true if prediction has hit a trigger_teleport + playerState_t predictedPlayerState; + centity_t predictedPlayerEntity; + qboolean validPPS; // clear until the first call to CG_PredictPlayerState + int predictedErrorTime; + vec3_t predictedError; + + float stepChange; // for stair up smoothing + int stepTime; + + float duckChange; // for duck viewheight smoothing + int duckTime; + + float landChange; // for landing hard + int landTime; + + // input state sent to server + int weaponSelect; + + // auto rotating items + vec3_t autoAngles; + vec3_t autoAxis[3]; + vec3_t autoAnglesFast; + vec3_t autoAxisFast[3]; + + // view rendering + refdef_t refdef; + vec3_t refdefViewAngles; // will be converted to refdef.viewaxis + + // zoom key + qboolean zoomed; + qboolean zoomLocked; + 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; + int scoreFadeTime; + char killerName[MAX_NAME_LENGTH]; + + // centerprinting + int centerPrintTime; + int centerPrintCharWidth; + int centerPrintY; + char centerPrint[1024]; + int centerPrintLines; + + // low ammo warning state + int lowAmmoWarning; // 1 = low, 2 = empty + + // kill timers for carnage reward + int lastKillTime; + + // crosshair client ID + int crosshairClientNum; + int crosshairClientTime; + + // powerup active flashing + int powerupActive; + int powerupTime; + + // attacking player + int attackerTime; + + // reward medals + int rewardTime; + int rewardCount; + qhandle_t rewardShader; + + // warmup countdown + int warmup; + int warmupCount; + + //========================== + + int itemPickup; + int itemPickupTime; + int itemPickupBlendTime; // the pulse around the crosshair is timed seperately + + int weaponSelectTime; + int weaponAnimation; + int weaponAnimationTime; + + // blend blobs + float damageTime; + float damageX, damageY, damageValue, damageShieldValue; + + // status bar head + //RPG-X | Phenix | 09/06/2005 + // Raven commented this out! + float headYaw; + float headEndPitch; + float headEndYaw; + int headEndTime; + float headStartPitch; + float headStartYaw; + int headStartTime; + + int interfaceStartupTime; // Timer for menu graphics + int interfaceStartupDone; // True when menu is done starting up + + // view movement + float v_dmg_time; + float v_dmg_pitch; + float v_dmg_roll; + + vec3_t kick_angles; // weapon kicks + vec3_t kick_origin; + + // temp working variables for player view + float bobfracsin; + int bobcycle; + float xyspeed; + + //Shake information + float shake_intensity; + int shake_duration; + int shake_start; + + int shake_serverIndex; //end time for the server so client-side shakes don't stop it + //TiM : RPG-X Shake Info To try and make it look better + int shake_nextLerp, shake_lastLerp; //next cg.time value where a new random lerp value will be chosen (Updating per frame makes me motion sick due to its l33t jerkiness ) + vec3_t shake_LerpAngle, shake_LerpOrigin; //Relevant data to lerp to + vec3_t shake_LastAngle, shake_LastOrigin; + + // development tool + refEntity_t testModelEntity; + char testModelName[MAX_QPATH]; + qboolean testGun; + + int loadLCARSStage; + int loadLCARScnt; + qboolean showObjectives; + int mod;//method O' death + + //RPG-X | Phenix | 08/06/2005 + //RPG-X | TiM | 13/2/2006 - Moved here for standardisation + int adminMsgTime; + char adminMsgMsg[MAX_OBJ_LENGTH]; + + //TiM : ThirdPersonZoom Booleans + //These are inserted into the thirdperson camera func + qboolean zoomedForward; + qboolean zoomedBackward; + qboolean zoomedLeft; + qboolean zoomedRight; + qboolean zoomedUp; + qboolean zoomedDown; + + qboolean zoomAngleLeft; + qboolean zoomAngleRight; + + qboolean zoomPitchDown; + qboolean zoomPitchUp; + + //TiM - The body in FPS mode + fpsBody_t fpsBody; + + //TiM - A local boolean for the TPS view + qboolean thirdPersonNoLerp; +} cg_t; + + +typedef enum +{ + MT_NONE = 0, + MT_METAL, + MT_GLASS, + MT_GLASS_METAL, + MT_WOOD, + MT_STONE, + + NUM_CHUNK_TYPES + +} material_type_t; + +#define NUM_CHUNKS 6 + +// all of the model, shader, and sound references that are +// loaded at gamestate time are stored in cgMedia_t +// Other media that can be tied to clients, weapons, or items are +// stored in the clientInfo_t, itemInfo_t, weaponInfo_t, and powerupInfo_t +typedef struct { + qhandle_t charsetShader; + qhandle_t charsetPropTiny; + qhandle_t charsetProp; + qhandle_t charsetPropBig; +// qhandle_t charsetPropGlow; + qhandle_t charsetPropB; + qhandle_t whiteShader; + qhandle_t white2Shader; + + qhandle_t redFlagModel; + qhandle_t blueFlagModel; + qhandle_t redFlagShader[4]; + qhandle_t blueFlagShader[4]; + + qhandle_t teamStatusBar; + + qhandle_t deferShader; + //qhandle_t eliminatedShader; + +// recently added Trek shaders + + qhandle_t phaserShader; + qhandle_t phaserEmptyShader; + qhandle_t phaserAltShader; + qhandle_t phaserAltEmptyShader; + qhandle_t phaserMuzzleEmptyShader; + + qhandle_t testDetpackShader3; + qhandle_t testDetpackRingShader1; + qhandle_t testDetpackRingShader2; + qhandle_t testDetpackRingShader3; + qhandle_t testDetpackRingShader4; + qhandle_t testDetpackRingShader5; + qhandle_t testDetpackRingShader6; + + qhandle_t detpackPlacedIcon; + + //qhandle_t dnBoltShader; + + //qhandle_t stasisRingShader; + //qhandle_t stasisAltShader; + + qhandle_t disruptorBolt; + qhandle_t disruptorStreak; + + qhandle_t whiteRingShader; + qhandle_t orangeRingShader; + qhandle_t quantumExplosionShader; + qhandle_t quantumFlashShader; + + //qhandle_t scavengerAltShader; + + /*qhandle_t imodExplosionShader; + qhandle_t IMODShader; + qhandle_t IMOD2Shader; + qhandle_t altIMODShader; + qhandle_t altIMOD2Shader;*/ + + qhandle_t prifleBeam; + qhandle_t prifleImpactShader; + qhandle_t compressionAltBeamShader; + qhandle_t compressionAltBlastShader; + qhandle_t prifleBolt; + qhandle_t flashlightModel; + + qhandle_t disruptorBeam; + //qhandle_t greenBurstShader; + //qhandle_t greenTrailShader; + //qhandle_t tetrionTrail2Shader; + //qhandle_t tetrionFlareShader; + qhandle_t borgFlareShader; + //qhandle_t bulletmarksShader; + qhandle_t borgLightningShaders[4]; + + //qhandle_t bigBoomShader; + //qhandle_t scavExplosionFastShader; + //qhandle_t scavExplosionSlowShader; + + qhandle_t sunnyFlareShader; + qhandle_t scavMarkShader; + qhandle_t sparkShader; + qhandle_t spark2Shader; + qhandle_t steamShader; + qhandle_t fireShader; //RPG-X | Marcin | 24/12/2008 + qhandle_t fire2Shader; //RPG-X | Marcin | 24/12/2008 + qhandle_t fire3Shader; //RPG-X | Marcin | 24/12/2008 + qhandle_t smokeShader; + //qhandle_t IMODMarkShader; + qhandle_t waterDropShader; + qhandle_t oilDropShader; + qhandle_t greenDropShader; + + //RPG-X | GSIO01 | 11/05/2009: + qhandle_t quantumGlow; + qhandle_t quantumRays; + qhandle_t photonGlow; + qhandle_t photonRay; + qhandle_t fireParticle; + + #ifdef XTRA + //RPG-X | GSIO01 | 08/03/2010: + qhandle_t snowShader; + qhandle_t waterShader; + #endif + + qhandle_t explosionModel; + qhandle_t nukeModel; + //qhandle_t electricalExplosionFastShader;// These are used to have a bit of variation in the explosions + qhandle_t electricalExplosionSlowShader; + qhandle_t surfaceExplosionShader; + //qhandle_t purpleParticleShader; + qhandle_t blueParticleShader; + qhandle_t ltblueParticleShader; + + qhandle_t yellowParticleShader; + qhandle_t orangeParticleShader; + qhandle_t dkorangeParticleShader; + qhandle_t orangeTrailShader; + qhandle_t compressionMarkShader; + + qhandle_t redFlareShader; + qhandle_t redRingShader; + qhandle_t redRing2Shader; + qhandle_t bigShockShader; + qhandle_t bolt2Shader; + qhandle_t quantumRingShader; + + //qhandle_t holoOuchShader; + qhandle_t painBlobShader; + qhandle_t painShieldBlobShader; + //qhandle_t shieldBlobShader; + //qhandle_t halfShieldShader; + //qhandle_t holoDecoyShader; + + qhandle_t trans1Shader; + qhandle_t trans2Shader; + + //TiM - SP Transporter FX shaders + qhandle_t transport1Shader; + qhandle_t transport2Shader; + + qhandle_t fountainShader; + qhandle_t rippleShader; +// end recently added Trek shaders + + //qhandle_t teamRedShader; + //qhandle_t teamBlueShader; + + //qhandle_t chatShader; + qhandle_t connectionShader; + + //qhandle_t selectShader; + //qhandle_t crosshairShader[NUM_CROSSHAIRS]; + qhandle_t laserShader; // LASER + + qhandle_t lagometerShader; + qhandle_t backTileShader; + //qhandle_t noammoShader; + + qhandle_t smokePuffRageProShader; + qhandle_t waterBubbleShader; + + qhandle_t numberShaders[11]; + qhandle_t smallnumberShaders[11]; + + qhandle_t shadowMarkShader; + + //qhandle_t botSkillShaders[5]; + + //TiM + //qhandle_t pClassShaders[NUM_PLAYER_CLASSES]; + //qhandle_t borgIconShader; + //qhandle_t borgQueenIconShader; + //qhandle_t heroSpriteShader; + //-TiM + + + // Zoom + qhandle_t zoomMaskShader; + qhandle_t zoomMask116Shader; + qhandle_t zoomGlow116Shader; + /*qhandle_t zoomBarShader; + qhandle_t zoomInsertShader; + qhandle_t zoomArrowShader; + qhandle_t ammoslider;*/ + + // wall mark shaders + qhandle_t wakeMarkShader; + qhandle_t holeMarkShader; + qhandle_t energyMarkShader; + + // powerup shaders + //qhandle_t quadShader; + //qhandle_t redQuadShader; + //qhandle_t quadWeaponShader; + //qhandle_t invisShader; + //qhandle_t regenShader; + //qhandle_t battleSuitShader; + //qhandle_t battleWeaponShader; + //qhandle_t hastePuffShader; + //qhandle_t flightPuffShader; + //qhandle_t seekerModel; + qhandle_t disruptorShader; + qhandle_t explodeShellShader; + qhandle_t quantumDisruptorShader; + qhandle_t borgFullBodyShieldShader; + + qhandle_t transportShader; //RPG-X : TiM - SP Transport Shader + qhandle_t transportKlingon; + qhandle_t transportRomulan; + qhandle_t transportCardassian; + + qhandle_t weaponPlaceholderShader; + qhandle_t rezOutShader; + qhandle_t electricBodyShader; + + //eyebeam/tripwire shaders + qhandle_t whiteLaserShader; + qhandle_t borgEyeFlareShader; + + // weapon effect models + //qhandle_t ringFlashModel; + + // weapon effect shaders + //qhandle_t bloodExplosionShader; + + // special effects models + qhandle_t teleportEffectModel; + qhandle_t teleportEffectShader; + qhandle_t shieldActivateShaderBlue; + //RPG-X START | GSIO01 | 09/05/2009 + qhandle_t shieldActivateShaderBorg; + qhandle_t shieldActivateShaderYellow; + //RPG-Y END + //qhandle_t shieldDamageShaderBlue; + qhandle_t shieldActivateShaderRed; + qhandle_t shieldDamageShaderRed; + + qhandle_t holoDoorModel; + qhandle_t chunkModels[NUM_CHUNK_TYPES][NUM_CHUNKS]; + + //RPG-X: RedTechie - Register Endcaps for scoreboard + qhandle_t scoreboardtopleft; + qhandle_t scoreboardtopright; + qhandle_t scoreboardbotleft; + qhandle_t scoreboardbotright; + + //RPG-X: RedTechie - Register new healthbar + qhandle_t healthbigcurve; + qhandle_t healthendcap; + + //TiM + qhandle_t healthSineWave; + + //RPG-X: RedTechie - Registered Cloak Sprite + qhandle_t cloakspriteShader; + + qhandle_t scoreboardEndcap; + qhandle_t weaponbox; + qhandle_t weaponbox2; + qhandle_t corner_12_24; + qhandle_t corner_8_16_b; + + qhandle_t weaponcap1; + qhandle_t weaponcap2; + + // medals shown during gameplay + /*qhandle_t medalImpressive; + qhandle_t medalExcellent; + qhandle_t medalFirstStrike; + qhandle_t medalAce; + qhandle_t medalExpert; + qhandle_t medalMaster; + qhandle_t medalChampion;*/ + + // Holodeck doors + //qhandle_t doorbox; + + //RPG-X Mod + //qhandle_t rpgxlogo; + qhandle_t greenParticleShader; + qhandle_t greenParticleStreakShader; + //qhandle_t blueParticleStreakShader; + + qhandle_t liteRedParticleStreakShader; + qhandle_t liteRedParticleShader; + + qhandle_t probeBeam; + qhandle_t probeDecal; + + qhandle_t regenDecal; + + //RPG-X : Weapon Holsters + qhandle_t phaserHolster; + qhandle_t phaserHolsterInner; + + qhandle_t tricorderHolster; + qhandle_t tricorderHolsterInner; + + //TR-116 Scope + qhandle_t tr116EyeScope; + + //SIMS module + qhandle_t simsModule; + + //EVA + qhandle_t evaInterior; + + qhandle_t medicalScanner; + + //hazard Helmet + qhandle_t hazardHelmet; + + // sounds + //sfxHandle_t quadSound; + //sfxHandle_t selectSound; + sfxHandle_t useNothingSound; + //sfxHandle_t wearOffSound; + sfxHandle_t footsteps[FOOTSTEP_TOTAL][4]; + sfxHandle_t holoOpenSound; + sfxHandle_t teleInSound; + //sfxHandle_t teleOutSound; + //RPG-X + sfxHandle_t transportSound; + //sfxHandle_t noAmmoSound; + sfxHandle_t respawnSound; + sfxHandle_t talkSound; + //sfxHandle_t landSound; + sfxHandle_t landSound[LANDSOUND_TOTAL]; //RPG-X | GSIO01 | 20/05/2009 + sfxHandle_t jumpPadSound; + + sfxHandle_t oneMinuteSound; + sfxHandle_t fiveMinuteSound; + sfxHandle_t suddenDeathSound; + + //sfxHandle_t threeFragSound; + //sfxHandle_t twoFragSound; + //sfxHandle_t oneFragSound; + + //sfxHandle_t hitSound; + //sfxHandle_t shieldHitSound; + //sfxHandle_t shieldPierceSound; + //sfxHandle_t hitTeamSound; + + //sfxHandle_t rewardImpressiveSound; + //sfxHandle_t rewardExcellentSound; + //sfxHandle_t rewardDeniedSound; + //sfxHandle_t rewardFirstStrikeSound; + //sfxHandle_t rewardAceSound; + //sfxHandle_t rewardExpertSound; + //sfxHandle_t rewardMasterSound; + //sfxHandle_t rewardChampionSound; + + //sfxHandle_t takenLeadSound; + //sfxHandle_t tiedLeadSound; + //sfxHandle_t lostLeadSound; + + sfxHandle_t watrInSound; + sfxHandle_t watrOutSound; + sfxHandle_t watrUnSound; + + //sfxHandle_t flightSound; + sfxHandle_t medkitSound; + sfxHandle_t borgBeamInSound; + + // Interface sounds + sfxHandle_t interfaceSnd1; + + // teamplay sounds + //sfxHandle_t redLeadsSound; + //sfxHandle_t blueLeadsSound; + //sfxHandle_t teamsTiedSound; + + // tournament sounds + sfxHandle_t count3Sound; + sfxHandle_t count2Sound; + sfxHandle_t count1Sound; + sfxHandle_t countFightSound; + sfxHandle_t countPrepareSound; + + // CTF sounds + /*sfxHandle_t ctfStealSound; + sfxHandle_t ctfReturnSound; + sfxHandle_t ctfScoreSound; + sfxHandle_t ctfYouStealVoiceSound; + sfxHandle_t ctfYouDroppedVoiceSound; + sfxHandle_t ctfYouReturnVoiceSound; + sfxHandle_t ctfYouScoreVoiceSound; + sfxHandle_t ctfTheyStealVoiceSound; + sfxHandle_t ctfTheyDroppedVoiceSound; + sfxHandle_t ctfTheyReturnVoiceSound; + sfxHandle_t ctfTheyScoreVoiceSound; */ + + // + // trek sounds + // + sfxHandle_t envSparkSound1; + sfxHandle_t envSparkSound2; + sfxHandle_t envSparkSound3; + sfxHandle_t defaultPickupSound; + sfxHandle_t grenadeAltStickSound; + sfxHandle_t grenadeBounceSound1; + sfxHandle_t grenadeBounceSound2; + sfxHandle_t grenadeExplodeSound; + //sfxHandle_t tetrionRicochetSound1; + //sfxHandle_t tetrionRicochetSound2; + //sfxHandle_t tetrionRicochetSound3; + sfxHandle_t invulnoProtectSound; + //sfxHandle_t regenSound; + //sfxHandle_t poweruprespawnSound; + sfxHandle_t disintegrateSound; + sfxHandle_t disintegrate2Sound; + sfxHandle_t playerExplodeSound; + sfxHandle_t waterDropSound1; + sfxHandle_t waterDropSound2; + sfxHandle_t waterDropSound3; + sfxHandle_t holoInitSound; + sfxHandle_t holoDoorSound; + sfxHandle_t holoFadeSound; + sfxHandle_t phaserEmptySound; + sfxHandle_t quantumBoom; + //RPG-X: RedTechie - Better explo sounds for glauncher + sfxHandle_t grenadeAltExplodeSnd; + //RPG-X: RedTechie - Shake command sound + sfxHandle_t ShakeSound; + //RPG-X | Phenix | 13/02/2005 + sfxHandle_t N00bSound[N00bSoundCount]; + //RPG-X | Phenix | 08/06/2005 + sfxHandle_t AdminMsgSound; + //RPG-X | TiM + sfxHandle_t splatSound; + //RPG-X | TiM + sfxHandle_t tedTextSound; + + // Zoom + sfxHandle_t zoomStart; + sfxHandle_t zoomStart116; + sfxHandle_t zoomLoop; + sfxHandle_t zoomEnd; + sfxHandle_t zoomEnd116; + sfxHandle_t tr116Chirp; + sfxHandle_t tr116Whir; + + // Chunk sounds + sfxHandle_t metalChunkSound; + sfxHandle_t glassChunkSound; + sfxHandle_t woodChunkSound; + sfxHandle_t stoneChunkSound; + + //TiM : Splosion sounds + sfxHandle_t surfaceExpSound[3]; + sfxHandle_t electricExpSound[3]; + sfxHandle_t bigSurfExpSound; + + //TiM - Q Flash sound + sfxHandle_t qFlash; + + qhandle_t loading1; + qhandle_t loading2; + qhandle_t loading3; + qhandle_t loading4; + qhandle_t loading5; + qhandle_t loading6; + qhandle_t loading7; + qhandle_t loading8; + qhandle_t loading9; + qhandle_t loadingcircle; + qhandle_t loadingquarter; + qhandle_t loadingcorner; + qhandle_t loadingtrim; + qhandle_t circle; + qhandle_t circle2; + qhandle_t corner_12_18; + qhandle_t halfroundr_22; + qhandle_t corner_ul_20_30; + qhandle_t corner_ll_8_30; + + //TiM - OPTIMIZATION: + //Used 2 API functions to cut down the + //shader usage on this thing significantly ^_^ + qhandle_t radarShader; + /*qhandle_t rd_up; + qhandle_t rd_down; + qhandle_t rd_level; + qhandle_t rd_red_up; + qhandle_t rd_red_down; + qhandle_t rd_red_level; + qhandle_t rd_blue_up; + qhandle_t rd_blue_down; + qhandle_t rd_blue_level; + qhandle_t rd_white_up; + qhandle_t rd_white_down; + qhandle_t rd_white_level; + qhandle_t rd_teal_up; + qhandle_t rd_teal_down; + qhandle_t rd_teal_level; + qhandle_t rd_black_up; + qhandle_t rd_black_down; + qhandle_t rd_black_level; + qhandle_t rd_injured_up; + qhandle_t rd_injured_down;*/ + qhandle_t rd_injured_level; + + qhandle_t radarMain; + + //RPG-X (J2J) Rank Images for Score Board (Arrays for each class type) + //TiM: *shudder* There was a better way! + /*qhandle_t ri_Civ; + qhandle_t ri_Crewman[4]; + qhandle_t ri_Cadet1[4]; + qhandle_t ri_Cadet2[4]; + qhandle_t ri_Cadet3[4]; + qhandle_t ri_Cadet4[4]; + qhandle_t ri_Ensign[4]; + qhandle_t ri_Ltjg[4]; + qhandle_t ri_Lt[4]; + qhandle_t ri_Ltcmdr[4]; + qhandle_t ri_Cmdr[4]; + qhandle_t ri_Capt[4]; + qhandle_t ri_Cmmdr[4]; + qhandle_t ri_Admr2[4]; + qhandle_t ri_Admr3[4]; + qhandle_t ri_Admr4[4]; + qhandle_t ri_Admr5[4];*/ + + //RPG-X: J2J - This is for sharky's crosshiar for each weapon. + //TiM: hrmmmm..... O_o + //qhandle_t crosshair[15]; + qhandle_t crosshairSheet; + + //TiM : LensFlare Parts + qhandle_t flareCore; + qhandle_t flareStreak; + qhandle_t flareChroma; + qhandle_t flareRadial; + qhandle_t flareStraight; + qhandle_t flareInverseRad; + qhandle_t flareHaze; + + qhandle_t orangeStarShader; + + //Q Flash sprite + qhandle_t qFlashSprite; + +} cgMedia_t; + + +// The client game static (cgs) structure hold everything +// loaded or calculated from the gamestate. It will NOT +// be cleared when a tournement restart is done, allowing +// all clients to begin playing instantly +typedef struct { + gameState_t gameState; // gamestate from server + glconfig_t glconfig; // rendering configuration + float screenXScale; // derived from glconfig + float screenYScale; + float screenXBias; + + int serverCommandSequence; // reliable command stream counter + int processedSnapshotNum;// the number of snapshots cgame has requested + + qboolean localServer; // detected on startup by checking sv_running + + // parsed from serverinfo + gametype_t gametype; + qboolean pModAssimilation; + qboolean pModDisintegration; + qboolean pModActionHero; + qboolean pModSpecialties; + qboolean pModElimination; + int dmflags; + int teamflags; + int fraglimit; + int capturelimit; + int timelimit; + int maxclients; + char mapname[MAX_QPATH]; + qboolean ForceClassColor; + char rankSet[MAX_QPATH]; + char classSet[MAX_QPATH]; + + int voteTime; + int voteYes; + int voteNo; + qboolean voteModified; // beep whenever changed + char voteString[MAX_STRING_TOKENS]; + + int levelStartTime; + + int scores1, scores2; // from configstrings + int redflag, blueflag; // flag status from configstrings + + // + // locally derived information from gamestate + // + qhandle_t gameModels[MAX_MODELS]; +// qhandle_t useableModels[HI_NUM_HOLDABLE]; + sfxHandle_t gameSounds[MAX_SOUNDS]; + + int numInlineModels; + qhandle_t inlineDrawModel[MAX_MODELS]; + vec3_t inlineModelMidpoints[MAX_MODELS]; + + clientInfo_t clientinfo[MAX_CLIENTS]; + + // teamchat width is *3 because of embedded color codes + char teamChatMsgs[TEAMCHAT_HEIGHT][TEAMCHAT_WIDTH*3+1]; + int teamChatMsgTimes[TEAMCHAT_HEIGHT]; + int teamChatPos; + int teamLastChatPos; + + // media + cgMedia_t media; + + //TiM: ranks data + defaultRankData_t defaultRankData; + ranksData_t ranksData[MAX_RANKS]; + + //TiM: crosshair data + crosshairsData_t crosshairsData[MAX_CROSSHAIRS]; + + //decoy memory spaces + clientInfo_t decoyInfo[MAX_DECOYS]; + + //During game load, check to see if we have any locations + //so we may edit the scoreboard to account for them + qboolean locations; + + //TiM - static class data + cg_classData_t classData[MAX_CLASSES]; + + //objectives + objective_t objectives[MAX_OBJECTIVES]; + + //widescreen variables + widescreen_t widescreen; + + //scannable tricorder dynamic text + char scannableStrings[MAX_SCANNABLES][36]; + + qboolean scannablePanels; +} cgs_t; + +//============================================================================== + +extern cgs_t cgs; +extern cg_t cg; +extern centity_t cg_entities[MAX_GENTITIES]; +extern weaponInfo_t cg_weapons[MAX_WEAPONS]; +extern itemInfo_t cg_items[MAX_ITEMS]; +extern markPoly_t cg_markPolys[MAX_MARK_POLYS]; + +extern vmCvar_t cg_centertime; +extern vmCvar_t cg_runpitch; +extern vmCvar_t cg_runroll; +extern vmCvar_t cg_bobup; +extern vmCvar_t cg_bobpitch; +extern vmCvar_t cg_bobroll; +extern vmCvar_t cg_swingSpeed; +extern vmCvar_t cg_shadows; +extern vmCvar_t cg_gibs; +extern vmCvar_t cg_drawTimer; +extern vmCvar_t cg_drawFPS; +extern vmCvar_t cg_drawSnapshot; +extern vmCvar_t cg_draw3dIcons; +extern vmCvar_t cg_drawIcons; +extern vmCvar_t cg_drawAmmoWarning; +extern vmCvar_t cg_drawCrosshair; +extern vmCvar_t cg_drawCrosshairNames; +extern vmCvar_t cg_dynamicCrosshairNames; +extern vmCvar_t cg_drawRewards; +extern vmCvar_t cg_drawTeamOverlay; +extern vmCvar_t cg_teamOverlayUserinfo; +extern vmCvar_t cg_crosshairX; +extern vmCvar_t cg_crosshairY; +extern vmCvar_t cg_crosshairSize; +extern vmCvar_t cg_crosshairHealth; +extern vmCvar_t cg_drawStatus; +extern vmCvar_t cg_draw2D; +extern vmCvar_t cg_animSpeed; +extern vmCvar_t cg_debugAnim; +extern vmCvar_t cg_debugPosition; +extern vmCvar_t cg_debugEvents; +extern vmCvar_t cg_errorDecay; +extern vmCvar_t cg_nopredict; +extern vmCvar_t cg_noPlayerAnims; +extern vmCvar_t cg_showmiss; +extern vmCvar_t cg_footsteps; +extern vmCvar_t cg_addMarks; +extern vmCvar_t cg_gun_frame; +extern vmCvar_t cg_gun_x; +extern vmCvar_t cg_gun_y; +extern vmCvar_t cg_gun_z; +extern vmCvar_t cg_drawGun; +extern vmCvar_t cg_viewsize; +extern vmCvar_t cg_autoswitch; +extern vmCvar_t cg_ignore; +extern vmCvar_t cg_simpleItems; +extern vmCvar_t cg_fov; +extern vmCvar_t cg_zoomFov; +extern vmCvar_t cg_thirdPersonRange; +extern vmCvar_t cg_thirdPersonAngle; +extern vmCvar_t cg_thirdPersonVertOffset; //RPG-X: TiM +extern vmCvar_t cg_thirdPersonHorzOffset; +extern vmCvar_t cg_thirdPersonAlpha; +extern vmCvar_t cg_thirdPersonCameraDamp; +extern vmCvar_t cg_thirdPersonTargetDamp; +extern vmCvar_t cg_thirdPersonPitchOffset; +extern vmCvar_t cg_thirdPerson; +extern vmCvar_t cg_stereoSeparation; +extern vmCvar_t cg_lagometer; +extern vmCvar_t cg_drawAttacker; +extern vmCvar_t cg_synchronousClients; +extern vmCvar_t cg_teamChatTime; +extern vmCvar_t cg_teamChatHeight; +extern vmCvar_t cg_stats; +extern vmCvar_t cg_reportDamage; +extern vmCvar_t cg_forceModel; +extern vmCvar_t cg_buildScript; +extern vmCvar_t cg_paused; +extern vmCvar_t cg_blood; +extern vmCvar_t cg_predictItems; +extern vmCvar_t cg_deferPlayers; +extern vmCvar_t cg_disablekillmsgs; +extern vmCvar_t cg_drawradar; +extern vmCvar_t cg_noclipSpectate; //RPG-X J2J: Option for client to go no-clip in spectator mode. +extern vmCvar_t rpg_ctribgrenade; //RPG-X: RedTechie - Nevermind :P +extern vmCvar_t cg_dynamicCrosshair; //RPG-X | Phenix | 09/06/2005 +extern vmCvar_t doomHead; //RPG-X | Phenix | 09/06/2005 +extern vmCvar_t cg_dynamiclensflares; //RPG-X | TiM | 29/6/2005 +extern vmCvar_t cg_noTalkingHeads; +extern vmCvar_t cg_noFrowningHeads; +extern vmCvar_t cg_noBlinkingHeads; +extern vmCvar_t cg_noDynamicRanks; +extern vmCvar_t noAdminChat; +extern vmCvar_t cg_firstPersonBody; +extern vmCvar_t cg_defaultModel; + +extern vmCvar_t cg_defaultChar; + +//RPG-X: TiM - Player Model System Parameters +extern vmCvar_t pms_age; +extern vmCvar_t pms_height; +extern vmCvar_t pms_weight; +extern vmCvar_t pms_race; + +extern vmCvar_t emote_Offset; + +extern vmCvar_t cg_thirdPersonZoomRate; +extern vmCvar_t cg_showEntityNums; + +//TiM - SecurityCode +extern vmCvar_t sv_securityHash; +extern vmCvar_t sv_securityCode; + +extern vmCvar_t cg_handleWidescreen; +extern vmCvar_t ui_handleWidescreen; + +//extern vmCvar_t cg_chatBGColor; + +extern vmCvar_t cg_chatColor; + +//RPG-X | GSIO01 | 11/05/2009 +extern vmCvar_t rpg_forceFieldSet; + +// grp cvars +extern vmCvar_t grp_berp; + + + + + +qboolean CG_Cvar_ClampInt( const char *name, vmCvar_t *vmCvar, int min, int max ); + +// +// cg_main.c +// +const char *CG_ConfigString( int index ); +const char *CG_Argv( int arg ); + +void QDECL CG_Printf( const char *msg, ... ); +void QDECL CG_Error( const char *msg, ... ); +void CG_StartMusic( void ); + +void CG_UpdateCvars( void ); + +int CG_CrosshairPlayer( void ); +int CG_LastAttacker( void ); + + +// +// cg_view.c +// +void CG_TestModel_f (void); +void CG_TestGun_f (void); +void CG_TestModelNextFrame_f (void); +void CG_TestModelPrevFrame_f (void); +void CG_TestModelNextSkin_f (void); +void CG_TestModelPrevSkin_f (void); +void CG_ZoomDown_f( void ); +void CG_ZoomUp_f( void ); +void CG_CameraShake( float intensity, int duration, qboolean addRumbleSound ); + +void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); + + +// +// cg_drawtools.c +// +void CG_PrintInterfaceGraphics(int min,int max); +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 s1, + float t1, float s2, float t2, qhandle_t hShader ); +void CG_DrawString( float x, float y, const char *string, + float charWidth, float charHeight, const float *modulate ); +void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ); +void CG_LoadFonts(void); + + +void CG_DrawStringExt( int x, int y, const char *string, const float *setColor, + qboolean forceColor, qboolean shadow, int charWidth, int charHeight, int maxChars ); +void CG_DrawBigString( int x, int y, const char *s, float alpha ); +void CG_DrawBigStringColor( int x, int y, const char *s, vec4_t color ); +void CG_DrawSmallString( int x, int y, const char *s, float alpha ); +void CG_DrawSmallStringColor( int x, int y, const char *s, vec4_t color ); + +int CG_DrawStrlen( const char *str ); + +float *CG_FadeColor( int startMsec, int totalMsec ); +float *CG_TeamColor( int team ); +void CG_TileClear( void ); +void CG_ColorForHealth( vec4_t hcolor ); +void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ); + +int UI_ProportionalStringWidth( const char* str,int style ); + +qboolean CG_LoadRanks( void ); +qboolean CG_LoadCrosshairs( void ); +qboolean CG_LoadClasses( void ); +// +// cg_draw.c +// +typedef struct +{ + int type; // STRING or GRAPHIC + float timer; // When it changes + int x; // X position + int y; // Y positon + int width; // Graphic width + int height; // Graphic height + char *file; // File name of graphic/ text if STRING + qhandle_t graphic; // Handle of graphic if GRAPHIC + int min; + int max; + int color; // Normal color + int style; // Style of characters +// void *pointer; // To an address +} interfacegraphics_s; + +typedef enum +{ + IG_GROW, + + IG_HEALTH_START, + IG_HEALTH_BEGINCAP, + IG_HEALTH_BOX1, + IG_HEALTH_SLIDERFULL, + IG_HEALTH_SLIDEREMPTY, + IG_HEALTH_ENDCAP, + IG_HEALTH_COUNT, + IG_HEALTH_END, + + IG_ARMOR_START, + IG_ARMOR_BEGINCAP, + IG_ARMOR_BOX1, + IG_ARMOR_SLIDERFULL, + IG_ARMOR_SLIDEREMPTY, + IG_ARMOR_ENDCAP, + IG_ARMOR_COUNT, + IG_ARMOR_END, + + IG_AMMO_START, + IG_AMMO_UPPER_BEGINCAP, + IG_AMMO_UPPER_ENDCAP, + IG_AMMO_LOWER_BEGINCAP, + IG_AMMO_SLIDERFULL, + IG_AMMO_SLIDEREMPTY, + IG_AMMO_LOWER_ENDCAP, + IG_AMMO_COUNT, + IG_AMMO_END, + + IG_MAX +} interface_graphics_t; + + +extern interfacegraphics_s interface_graphics[IG_MAX]; + +#define SG_OFF 0 +#define SG_STRING 1 +#define SG_GRAPHIC 2 +#define SG_NUMBER 3 +#define SG_VAR 4 + + +extern int sortedTeamPlayers[TEAM_MAXOVERLAY]; +extern int numSortedTeamPlayers; +extern int drawTeamOverlayModificationCount; + +#ifdef XTRA +void CG_MotionBlur( void ); +#endif + +void CG_AddLagometerFrameInfo( void ); +void CG_AddLagometerSnapshotInfo( snapshot_t *snap ); +void CG_CenterPrint( const char *str, int y, int charWidth ); +void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t headAngles ); +void CG_DrawActive( stereoFrame_t stereoView ); +void CG_DrawFlagModel( float x, float y, float w, float h, int team ); +void CG_DrawTeamBackground( int x, int y, int w, int h, float alpha, int team, qboolean scoreboard ); +void CG_DrawNumField (int x, int y, int width, int value,int charWidth,int charHeight,int style); +void CG_DrawObjectiveInformation( void ); + + +// +// cg_player.c +// +void CG_PlayerShieldHit(int entitynum, vec3_t angles, int amount); +void CG_Player( centity_t *cent ); +void CG_ResetPlayerEntity( centity_t *cent ); +//void CG_AddRefEntityWithPowerups( refEntity_t *ent, int powerups, int eFlags, qboolean borg ); +//void CG_AddRefEntityWithPowerups( refEntity_t *ent, int powerups, int eFlags, beamData_t *beamData, qboolean borg ); +void CG_AddRefEntityWithPowerups( refEntity_t *ent, int powerups, int eFlags, beamData_t *beamData, int cloakTime, int decloakTime, qboolean borg ); +void CG_NewClientInfo( int clientNum ); +sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ); +void CG_NewDecoyInfo( int decoyNum ); + +// +// cg_predict.c +// +void CG_BuildSolidList( void ); +int CG_PointContents( const vec3_t point, int passEntityNum ); +void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask ); +void CG_PredictPlayerState( void ); +void CG_LoadDeferredPlayers( void ); + + +// +// cg_events.c +// +void CG_CheckEvents( centity_t *cent ); +const char *CG_PlaceString( int rank ); +void CG_EntityEvent( centity_t *cent, vec3_t position ); +void CG_PainEvent( centity_t *cent, int health ); + + +// +// cg_ents.c +// +void CG_SetEntitySoundPosition( centity_t *cent ); +void CG_AddPacketEntities( void ); +void CG_Beam( centity_t *cent ); +void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ); + +void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ); +void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ); + + +// +// cg_env.c +// +void CG_Spark( vec3_t origin, vec3_t dir, int delay, int killTime ); +void CG_Steam( vec3_t position, vec3_t dir, int killTime ); +void CG_Bolt( centity_t *cent ); +void CG_TransporterPad(vec3_t origin); +void CG_Drip(centity_t *cent, int killTime ); +void CG_Chunks( vec3_t origin, vec3_t dir, float size, material_type_t type ); + +//TiM +void CG_FountainSpurt( vec3_t org, vec3_t end ); +void CG_ElectricalExplosion( vec3_t start, vec3_t dir, float radius ); + +//RPG-X | GSIO01 | 09/05/2009 +void CG_PhaserFX(centity_t *cent); +void CG_TorpedoFX(centity_t *cent); +void CG_ParticleFire(vec3_t origin, int size); +void CG_ShowTrigger(centity_t *cent); + +// +// cg_weapons.c +// +void CG_NextWeapon_f( void ); +void CG_PrevWeapon_f( void ); +void CG_Weapon_f( void ); + +void CG_RegisterWeapon( int weaponNum ); +void CG_RegisterItemVisuals( int itemNum ); + +void CG_FireWeapon( centity_t *cent, qboolean alt_fire ); +void CG_FireSeeker( centity_t *cent ); +void CG_MissileHitWall( centity_t *cent, int weapon, vec3_t origin, vec3_t dir ); +void CG_MissileHitPlayer( centity_t *cent, int weapon, vec3_t origin, vec3_t dir); + +void CG_AddViewWeapon (playerState_t *ps); +void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent ); +void CG_DrawWeaponSelect( void ); +void CG_DrawWeaponIcon ( int x, int y, int weapon ); //RPG-X | Phenix | 08/06/2005 + +void CG_OutOfAmmoChange( qboolean altfire ); // should this be in pmove? +void CG_BounceEffect( centity_t *cent, int weapon, vec3_t origin, vec3_t normal ); +//void CG_BorgEyebeam( centity_t *cent, const refEntity_t *parent ); + +void CG_PlayShooterSound(centity_t *cent); + +extern int tris_state; + +// +// cg_marks.c +// +void CG_InitMarkPolys( void ); +void CG_AddMarks( void ); +void CG_ImpactMark( qhandle_t markShader, + const vec3_t origin, const vec3_t dir, + float orientation, + float r, float g, float b, float a, + qboolean alphaFade, + float radius, qboolean temporary ); + +// +// cg_localents.c +// +void CG_InitLocalEntities( void ); +localEntity_t *CG_AllocLocalEntity( void ); +localEntity_t CG_GetActiveList( void ); +void CG_AddLocalEntities( void ); + +// +// cg_effects.c +// +localEntity_t *CG_SmokePuff( const vec3_t p, + const vec3_t vel, + float radius, + float r, float g, float b, float a, + float duration, + int startTime, + int leFlags, + qhandle_t hShader ); +void CG_BubbleTrail( vec3_t start, vec3_t end, float spacing ); +void CG_SpawnEffect( vec3_t org, refEntity_t *ent_legs, refEntity_t *ent_torso, refEntity_t *ent_head); +void CG_QFlashEvent( vec3_t org ); + + +void CG_Bleed( vec3_t origin, int entityNum ); +void CG_Seeker( centity_t *cent ); + +//RPG-X: TiM : Smoke Ent +//void CG_Smoke( vec3_t origin, vec3_t dir, float radius, float speed, qhandle_t shader ); +void CG_Smoke( vec3_t position, vec3_t dir, int killTime, int radius ); +//RPG-X: Marcin: Fire +void CG_Fire( vec3_t position, vec3_t dir, int killTime, int radius, int fxEnt ); + +localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, + qhandle_t hModel, qhandle_t shader, int msec, float scale, + qboolean isSprite ); +localEntity_t *CG_MakeExplosion2( vec3_t origin, vec3_t dir, + qhandle_t hModel, int numFrames, qhandle_t shader, + int msec, qboolean isSprite, float scale, int flags ); + +void CG_SurfaceExplosion( vec3_t origin, vec3_t normal, float radius, float shake_speed, qboolean smoke ); +void CG_ExplosionEffects( vec3_t origin, int intensity, int radius); + +// +// cg_snapshot.c +// +void CG_ProcessSnapshots( void ); + +// +// cg_info.c +// +void CG_LoadingString( const char *s ); +void CG_LoadingItem( int itemNum ); +void CG_LoadingClient( int clientNum ); +void CG_DrawInformation( void ); + +// +// cg_scoreboard.c +// + +qboolean CG_DrawScoreboard( void ); +void CG_DrawTourneyScoreboard( void ); + +// +// these lists need to be sync'ed with stuff under g_log in g_local.h +// + +// awards +typedef enum { + AWARD_EFFICIENCY, // Accuracy + AWARD_SHARPSHOOTER, // Most compression rifle frags + AWARD_UNTOUCHABLE, // Perfect (no deaths) + AWARD_LOGISTICS, // Most pickups + AWARD_TACTICIAN, // Kills with all weapons + AWARD_DEMOLITIONIST, // Most explosive damage kills + AWARD_STREAK, // Ace/Expert/Master/Champion + AWARD_TEAM, // MVP/Defender/Warrior/Carrier/Interceptor/Bravery + AWARD_SECTION31, // All-around god + AWARD_MAX +} awardType_t; + +typedef enum +{ + TEAM_NONE = 0, // ha ha! you suck! + TEAM_MVP, // most overall points + TEAM_DEFENDER, // killed the most baddies near your flag + TEAM_WARRIOR, // most frags + TEAM_CARRIER, // infected the most people with plague + TEAM_INTERCEPTOR, // returned your own flag the most + TEAM_BRAVERY, // Red Shirt Award (tm). you died more than anybody. + TEAM_MAX +} teamAward_e; + +// +// above lists need to be sync'ed with stuff under g_log in g_local.h +// + +typedef enum +{ + AWARD_STREAK_NONE = 0, + AWARD_STREAK_ACE, + AWARD_STREAK_EXPERT, + AWARD_STREAK_MASTER, + AWARD_STREAK_CHAMPION, + AWARD_STREAK_MAX +} awardStreak_t; + + + + +extern char *cg_medalPicNames[]; +extern char *cg_medalSounds[]; + +extern char *cg_medalTeamPicNames[]; +extern char *cg_medalTeamSounds[]; + +extern char *cg_medalStreakPicNames[]; +extern char *cg_medalStreakSounds[]; + +void AW_SPPostgameMenu_f( void ); + + +// +// cg_consolecmds.c +// +qboolean CG_ConsoleCommand( void ); +void CG_InitConsoleCommands( void ); + +// +// cg_servercmds.c +// +void CG_ExecuteNewServerCommands( int latestSequence ); +void CG_ParseServerinfo( void ); +void CG_SetConfigValues( void ); + +// +// cg_playerstate.c +// +void CG_Respawn( void ); +void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ); + + +// cg_players.c +void updateSkin(int clientNum, char *new_model); + +/* +RPG-X J2J: This struct is used to convientently store the data loaded from TiM's + data.cfg file. References what body to use, skins etc.. + + TiM: Haha thanks Jay +*/ + +//typedef struct +//{ +// char ID[80]; +// char Name[255]; +// char BodyModel[255]; +// char Gender; +// qboolean Alien; +// qboolean Humanoid; +// char DefaultRank[255]; +// char SoundSet[255]; +////Allowed Classes +// qboolean Engineer; +// qboolean Science; +// qboolean Command; +// qboolean Security; +// qboolean Medical; +// +//} +//X_ModelCfgData; + +//=============================================== + +// +// system traps +// These functions are how the cgame communicates with the main game system +// + +// print message on the local console +void trap_Print( const char *fmt ); + +// abort the game +void trap_Error( const char *fmt ); + +// milliseconds should only be used for performance tuning, never +// for anything game related. Get time from the CG_DrawActiveFrame parameter +int trap_Milliseconds( void ); + +// console variable interaction +void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); +void trap_Cvar_Update( vmCvar_t *vmCvar ); +void trap_Cvar_Set( const char *var_name, const char *value ); +void trap_Cvar_Set_No_Modify( const char *var_name, const char *value ); +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); + +// ServerCommand and ConsoleCommand parameter access +int trap_Argc( void ); +void trap_Argv( int n, char *buffer, int bufferLength ); +void trap_Args( char *buffer, int bufferLength ); + +// filesystem access +// returns length of file +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +void trap_FS_Read( void *buffer, int len, fileHandle_t f ); +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); +void trap_FS_FCloseFile( fileHandle_t f ); + +// add commands to the local console as if they were typed in +// for map changing, etc. The command is not executed immediately, +// but will be executed in order the next time console commands +// are processed +void trap_SendConsoleCommand( const char *text ); + +// register a command name so the console can perform command completion. +// FIXME: replace this with a normal console command "defineCommand"? +void trap_AddCommand( const char *cmdName ); + +// send a string to the server over the network +void trap_SendClientCommand( const char *s ); + +// force a screen update, only used during gamestate load +void trap_UpdateScreen( void ); + +// model collision +void trap_CM_LoadMap( const char *mapname ); +int trap_CM_NumInlineModels( void ); +clipHandle_t trap_CM_InlineModel( int index ); // 0 = world, 1+ = bmodels +clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ); +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_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ); + +// a local sound is always played full volume +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ); +void trap_S_ClearLoopingSounds( void ); +void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, 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 ); // empty name stops music + + +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_RegisterShader3D( const char *name ); // returns all white if not found +qhandle_t trap_R_RegisterShaderNoMip( const char *name ); // returns all white if not found + +// a scene is built up by calls to R_ClearScene and the various R_Add functions. +// Nothing is drawn until R_RenderScene is called. +void trap_R_ClearScene( void ); +void trap_R_AddRefEntityToScene( const refEntity_t *re ); + +// polys are intended for simple wall marks, not really for doing +// significant construction +void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ); +void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); +void trap_R_RenderScene( const refdef_t *fd ); +void trap_R_SetColor( const float *rgba ); // NULL = 1,1,1,1 +void trap_R_DrawStretchPic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ); +void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); +void trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, + float frac, const char *tagName ); + +#ifdef XTRA +void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int numPolys ); +#endif + +// The glconfig_t will not change during the life of a cgame. +// If it needs to change, the entire cgame will be restarted, because +// all the qhandle_t are then invalid. +void trap_GetGlconfig( glconfig_t *glconfig ); + +// the gamestate should be grabbed at startup, and whenever a +// configstring changes +void trap_GetGameState( gameState_t *gamestate ); + +// cgame will poll each frame to see if a newer snapshot has arrived +// that it is interested in. The time is returned seperately so that +// snapshot latency can be calculated. +void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ); + +// a snapshot get can fail if the snapshot (or the entties it holds) is so +// old that it has fallen out of the client system queue +qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ); + +// retrieve a text command from the server stream +// the current snapshot will hold the number of the most recent command +// qfalse can be returned if the client system handled the command +// argc() / argv() can be used to examine the parameters of the command +qboolean trap_GetServerCommand( int serverCommandNumber ); + +// returns the most recent command number that can be passed to GetUserCmd +// this will always be at least one higher than the number in the current +// snapshot, and it may be quite a few higher if it is a fast computer on +// a lagged connection +int trap_GetCurrentCmdNumber( void ); + +qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ); + +// used for the weapon select and zoom +void trap_SetUserCmdValue( int stateValue, float sensitivityScale ); + +// aids for VM testing +void testPrintInt( char *string, int i ); +void testPrintFloat( char *string, float f ); + +int trap_MemoryRemaining( void ); + +#define MAX_LENS_FLARES 64 + +//RPG-X | TiM | 28/06/2005 +typedef struct { + int width; + int height; + float offset; + qboolean positive; + vec3_t color; + char* file; + qhandle_t graphic; +} lensReflec_s; + +typedef struct { + vec3_t worldCoord; + int w1; + int h1; + vec3_t glowColor; + float glowOffset; + float hazeOffset; + int minDist; + int maxDist; + vec3_t streakColor; + int streakDistMin; + int streakDistMax; + int streakW; + int streakH; + qboolean whiteStreaks; + int reflecDistMin; + int reflecDistMax; + qboolean reflecAnamorphic; + qboolean defReflecs; + qboolean clamp; + float maxAlpha; + int startTime; + int upTime; + int holdTime; + int downTime; + qboolean qfull; +} lensFlare_t; + +extern lensReflec_s lensReflec[10]; +//extern lensFlare_t lensFlare[MAX_LENS_FLARES]; + +void CG_DrawLensFlare( lensFlare_t *flare ); +void CG_InitLensFlare( vec3_t worldCoord, + int w1, int h1, + vec3_t glowColor, float glowOffset, float hazeOffset, int minDist, int maxDist, + vec3_t streakColor, int streakDistMin, int streakDistMax, int streakW, int streakH, qboolean whiteStreaks, + int reflecDistMin, int reflecDistMax, qboolean reflecAnamorphic, qboolean defReflecs, + qboolean clamp, float maxAlpha, int startTime, int upTime, int holdTime, int downTime ); + +//void CG_ParseClassData( void ); + +//TiM - Allow parts of the lerp code to update the 3rd view if need be +void CG_ResetThirdPersonViewDamp ( void ); + +#ifdef XTRA +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ); +void CG_ShaderStateChanged(void); + +#endif diff --git a/cgame/cg_localents.c b/cgame/cg_localents.c new file mode 100644 index 0000000..c8019c5 --- /dev/null +++ b/cgame/cg_localents.c @@ -0,0 +1,1309 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +// cg_localents.c -- every frame, generate renderer commands for locally +// processed entities, like smoke puffs, gibs, 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 tournement restarts +=================== +*/ +void CG_InitLocalEntities( void ) { + int i; + + memset( cg_localEntities, 0, sizeof( cg_localEntities ) ); + cg_activeLocalEntities.next = &cg_activeLocalEntities; + cg_activeLocalEntities.prev = &cg_activeLocalEntities; + cg_freeLocalEntities = cg_localEntities; + for ( i = 0 ; i < MAX_LOCAL_ENTITIES - 1 ; i++ ) { + cg_localEntities[i].next = &cg_localEntities[i+1]; + } +} + + +/* +================== +CG_FreeLocalEntity +================== +*/ +void CG_FreeLocalEntity( localEntity_t *le ) { + if ( !le->prev ) { + CG_Error( "CG_FreeLocalEntity: not active" ); + return; + } + + // 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; +} + +/* +================== +CG_GetActiveList +Accessor fn +================== +*/ +localEntity_t CG_GetActiveList( void ) +{ + return cg_activeLocalEntities; +} + +/* +==================================================================================== + +FRAGMENT PROCESSING + +A fragment localentity interacts with the environment in some way (hitting walls), +or generates more localentities along a trail. + +==================================================================================== +*/ + + +/* +================ +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 { + + } +} + +/* +===================================================================== + +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 +================== +*/ +void CG_AddMoveScaleFade( localEntity_t *le ) +{ + refEntity_t *re; + float c; + vec3_t delta; + float len; + + re = &le->refEntity; + + // fade / grow time + c = ( le->endTime - cg.time ) * le->lifeRate; + + re->shaderRGBA[3] = 0xff * c * le->color[3]; + + if ( !( le->leFlags & LEF_PUFF_DONT_SCALE ) ) { + re->data.sprite.radius = le->data.sprite.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->data.sprite.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. +=================== +*/ +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->data.sprite.radius = le->data.sprite.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->data.sprite.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. +================= +*/ +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->data.sprite.radius = le->data.sprite.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->data.sprite.radius ) { + CG_FreeLocalEntity( le ); + return; + } + + trap_R_AddRefEntityToScene( re ); +} + + + +/* +================ +CG_AddExplosion +================ +*/ +void CG_AddExplosion( localEntity_t *ex ) +{ + refEntity_t *ent; + + ent = &ex->refEntity; + + // calculate model frame + if ( ex->lifeRate > 0 ) { + float frac = (cg.time - ex->startTime) * ex->lifeRate; + int f = floor(frac); + if ( f < 0 ) { + f = 0; + } + + ent->frame = f + 1; + ent->oldframe = f; + ent->backlerp = 1.0 - ( frac - f ); + ent->renderfx |= RF_CAP_FRAMES; + } + // Explosions with zero shaders (using model default shader) don't fade, so + // allow fading when this flag is set. + if ( ex->leFlags & LEF_FADE_RGB ) + { + float frac = (float)( cg.time - ex->startTime )/(float)( ex->endTime - ex->startTime ); + + ent->shaderRGBA[0] = + ent->shaderRGBA[1] = + ent->shaderRGBA[2] = frac * 255; + ent->shaderRGBA[3] = 255; + } + + // add the entity + trap_R_AddRefEntityToScene(ent); + + // add the dlight + if ( ex->light ) { + float light; + + light = (float)( cg.time - ex->startTime ) / ( ex->endTime - ex->startTime ); + if ( light < 0.5 ) { + light = 1.0; + } else { + light = 1.0 - ( light - 0.5 ) * 2; + } + light = ex->light * light; + trap_R_AddLightToScene(ent->origin, light, ex->lightColor[0], ex->lightColor[1], ex->lightColor[2] ); + } +} + +/* +================ +CG_AddSpriteExplosion +================ +*/ +void CG_AddSpriteExplosion( localEntity_t *le ) +{ + refEntity_t re; + float c; + + re = le->refEntity; + + c = ( le->endTime - cg.time ) / ( float ) ( le->endTime - le->startTime ); + if ( c > 1 ) { + c = 1.0; // can happen during connection problems + } + + re.shaderRGBA[0] = 0xff; + re.shaderRGBA[1] = 0xff; + re.shaderRGBA[2] = 0xff; + re.shaderRGBA[3] = 0xff * c * 0.33; + + re.reType = RT_SPRITE; + re.data.sprite.radius = 42 * ( 1.0 - c ) + 30; + + trap_R_AddRefEntityToScene( &re ); + + // add the dlight + if ( le->light ) { + float light; + + light = (float)( cg.time - le->startTime ) / ( le->endTime - le->startTime ); + if ( light < 0.5 ) { + light = 1.0; + } else { + light = 1.0 - ( light - 0.5 ) * 2; + } + light = le->light * light; + trap_R_AddLightToScene(re.origin, light, le->lightColor[0], le->lightColor[1], le->lightColor[2] ); + } +} + + + + +/* +=================== +CG_AddScaleFadeSprite + +For trek, oriented sprites like blast rings and the like. +=================== +*/ +void CG_AddScaleFadeSprite( localEntity_t *le ) +{ + refEntity_t *re; + float c; + vec3_t delta; + float len; + + re = &le->refEntity; + + c = ( le->endTime - cg.time ) / ( float ) ( le->endTime - le->startTime ); + if ( c > 1 ) { + c = 1.0; // can happen during connection problems + } + + // Use the liferate to set the scale over time. + re->data.sprite.radius = le->data.sprite.radius + (le->lifeRate * (cg.time - le->startTime)); + if (re->data.sprite.radius <= 0) + { + CG_FreeLocalEntity( le ); + return; + } + + // 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->data.sprite.radius ) { + CG_FreeLocalEntity( le ); + return; + } + + // We will assume here that we want additive transparency effects. + re->shaderRGBA[0] = 0xff * c; + re->shaderRGBA[1] = 0xff * c; + re->shaderRGBA[2] = 0xff * c; + re->shaderRGBA[3] = 0xff * le->color[3]; + + re->reType = RT_ORIENTEDSPRITE; + + trap_R_AddRefEntityToScene( re ); +} + +/* +=================== +CG_AddQuad + +Of Trek, by Trek, and for Trek +=================== +*/ +void CG_AddQuad( localEntity_t *le ) +{ + refEntity_t *re; + float frac, alpha; + vec3_t delta; + float len; + vec3_t curRGB; + + re = &le->refEntity; + + frac = ( cg.time - le->startTime ) / ( float ) ( le->endTime - le->startTime ); + if ( frac > 1 ) + frac = 1.0; // can happen during connection problems + else if (frac < 0) + frac = 0.0; + + // Use the liferate to set the scale over time. + re->data.sprite.radius = le->data.sprite.radius + (le->data.sprite.dradius*frac); + if (re->data.sprite.radius <= 0) + { + CG_FreeLocalEntity( le ); + return; + } + + // 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->data.sprite.radius ) { + CG_FreeLocalEntity( le ); + return; + } + + // Calculate the current alpha. + alpha = le->alpha + (le->dalpha * frac); + VectorMA(le->data.sprite.startRGB, frac, le->data.sprite.dRGB, curRGB); + re->shaderRGBA[0] = 0xff * alpha * curRGB[0]; + re->shaderRGBA[1] = 0xff * alpha * curRGB[1]; + re->shaderRGBA[2] = 0xff * alpha * curRGB[2]; + re->shaderRGBA[3] = 0xff; + + re->reType = RT_ORIENTEDSPRITE; + + trap_R_AddRefEntityToScene( re ); +} + + +/* +=================== +CG_AddLine + +For trek, for beams and the like. +=================== +*/ +void CG_AddLine( localEntity_t *le ) +{ + refEntity_t *re; + float frac, alpha; + + re = &le->refEntity; + + frac = (cg.time - le->startTime) / ( float ) ( le->endTime - le->startTime ); + if ( frac > 1 ) + frac = 1.0; // can happen during connection problems + else if (frac < 0) + frac = 0.0; + + // Use the liferate to set the scale over time. + re->data.line.width = le->data.line.width + (le->data.line.dwidth * frac); + if (re->data.line.width <= 0) + { + CG_FreeLocalEntity( le ); + return; + } + + // We will assume here that we want additive transparency effects. + alpha = le->alpha + (le->dalpha * frac); + re->shaderRGBA[0] = 0xff * alpha; + re->shaderRGBA[1] = 0xff * alpha; + re->shaderRGBA[2] = 0xff * alpha; + re->shaderRGBA[3] = 0xff * alpha; // Yes, we could apply c to this too, but fading the color is better for lines. + + re->reType = RT_LINE; + + trap_R_AddRefEntityToScene( re ); +} + + +/* +=================== +CG_AddOLine + +For trek, for rectangles. +=================== +*/ +void CG_AddOLine( localEntity_t *le ) +{ + refEntity_t *re; + float frac, alpha; + + re = &le->refEntity; + + frac = (cg.time - le->startTime) / ( float ) ( le->endTime - le->startTime ); + if ( frac > 1 ) + frac = 1.0; // can happen during connection problems + else if (frac < 0) + frac = 0.0; + + // Use the liferate to set the scale over time. + re->data.line.width = le->data.line.width + (le->data.line.dwidth * frac); + if (re->data.line.width <= 0) + { + CG_FreeLocalEntity( le ); + return; + } + + // We will assume here that we want additive transparency effects. + alpha = le->alpha + (le->dalpha * frac); + re->shaderRGBA[0] = 0xff * alpha; + re->shaderRGBA[1] = 0xff * alpha; + re->shaderRGBA[2] = 0xff * alpha; + re->shaderRGBA[3] = 0xff * alpha; // Yes, we could apply c to this too, but fading the color is better for lines. + + re->reType = RT_ORIENTEDLINE; + + trap_R_AddRefEntityToScene( re ); +} + +/* +=================== +CG_AddLine2 + +For trek, for beams and the like. +=================== +*/ +void CG_AddLine2( localEntity_t *le ) +{ + refEntity_t *re; + float frac, alpha; + vec3_t curRGB; + + re = &le->refEntity; + + frac = (cg.time - le->startTime) / ( float ) ( le->endTime - le->startTime ); + if ( frac > 1 ) + frac = 1.0; // can happen during connection problems + else if (frac < 0) + frac = 0.0; + + // Use the liferate to set the scale over time. + re->data.line.width = le->data.line2.width + (le->data.line2.dwidth * frac); + re->data.line.width2 = le->data.line2.width2 + (le->data.line2.dwidth2 * frac); + if (re->data.line.width <= 0) + { + CG_FreeLocalEntity( le ); + return; + } + + // We will assume here that we want additive transparency effects. + alpha = le->alpha + (le->dalpha * frac); + VectorMA(le->data.line2.startRGB, frac, le->data.line2.dRGB, curRGB); + re->shaderRGBA[0] = 0xff * alpha * curRGB[0]; + re->shaderRGBA[1] = 0xff * alpha * curRGB[1]; + re->shaderRGBA[2] = 0xff * alpha * curRGB[2]; + re->shaderRGBA[3] = 0xff * alpha; // Yes, we could apply c to this too, but fading the color is better for lines. + + re->reType = RT_LINE2; + + trap_R_AddRefEntityToScene( re ); +} + + + +/* +=================== +CG_AddTrail + +For trek, for sparks and the like. +=================== +*/ +void CG_AddTrail( localEntity_t *le ) +{ + refEntity_t *re; + float frac, length, alpha; + vec3_t dir; + trace_t trace; + vec3_t curRGB; + + re = &le->refEntity; + + frac = (cg.time - le->startTime) / ( float ) ( le->endTime - le->startTime ); + if ( frac > 1 ) + frac = 1.0; // can happen during connection problems + else if (frac < 0) + frac = 0.0; + + // Use the liferate to set the scale over time. + re->data.line.width = le->data.trail.width + (le->data.trail.dwidth * frac); + if (re->data.line.width <= 0) + { + CG_FreeLocalEntity( le ); + return; + } + + if (!le->leFlags & LEF_MOVE) + { + return; + } + + // kef -- do these two lines _before_ copying origin into oldorigin + VectorSubtract(re->oldorigin, re->origin, dir); + VectorNormalize(dir); + + VectorCopy(re->origin, re->oldorigin); + BG_EvaluateTrajectory( &le->pos, cg.time, re->origin ); + + if (le->leFlags & LEF_USE_COLLISION) + { + // trace a line from previous position to new position + CG_Trace( &trace, re->oldorigin, NULL, NULL, re->origin, -1, CONTENTS_SOLID ); + + if ( trace.fraction != 1.0 ) + { // Hit something. + // 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; + } + + // reflect the velocity on the trace plane + CG_ReflectVelocity( le, &trace ); + } + VectorSubtract(re->oldorigin, re->origin, dir); + VectorNormalize(dir); + } + + // Set the length based on the velocity of the bit. + length = le->data.trail.length + (le->data.trail.dlength * frac); + if (length <= 0) + { + CG_FreeLocalEntity( le ); + return; + } + + VectorMA(re->origin, length, dir, re->oldorigin); + + // We will assume here that we want additive transparency effects. + alpha = le->alpha + (le->dalpha * frac); + VectorMA(le->data.trail.startRGB, frac, le->data.trail.dRGB, curRGB); + re->shaderRGBA[0] = 0xff * alpha * curRGB[0]; + re->shaderRGBA[1] = 0xff * alpha * curRGB[1]; + re->shaderRGBA[2] = 0xff * alpha * curRGB[2]; + re->shaderRGBA[3] = 0xff; // Yes, we could apply c to this too, but fading the color is better for lines. + + re->reType = RT_LINE; + + trap_R_AddRefEntityToScene( re ); +} + + + +/* +=================== +CG_AddViewSprite + +For trek, view sprites like smoke and the like. +=================== +*/ +void CG_AddViewSprite( localEntity_t *le ) +{ + refEntity_t *re; + float frac, alpha; + vec3_t delta; + float len; + trace_t trace; + vec3_t curRGB; + + re = &le->refEntity; + + frac = ( cg.time - le->startTime ) / ( float ) ( le->endTime - le->startTime ); + if ( frac > 1 ) + frac = 1.0; // can happen during connection problems + else if (frac < 0) + frac = 0.0; + + // Use the liferate to set the scale over time. + re->data.sprite.radius = le->data.sprite.radius + (le->data.sprite.dradius*frac); + if (re->data.sprite.radius <= 0) + { + CG_FreeLocalEntity( le ); + return; + } + + if (le->leFlags & LEF_MOVE) + { + VectorCopy(re->origin, re->oldorigin); + BG_EvaluateTrajectory( &le->pos, cg.time, re->origin ); + + if (le->leFlags & LEF_USE_COLLISION) + { + // trace a line from previous position to new position + CG_Trace( &trace, re->oldorigin, NULL, NULL, re->origin, -1, CONTENTS_SOLID ); + + if ( trace.fraction != 1.0 ) + { // Hit something. + // 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; + } + + // reflect the velocity on the trace plane + CG_ReflectVelocity( le, &trace ); + } + } + } + + // 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->data.sprite.radius ) { + CG_FreeLocalEntity( le ); + return; + } + + // Calculate the current alpha. + alpha = le->alpha + (le->dalpha * frac); + VectorMA(le->data.sprite.startRGB, frac, le->data.sprite.dRGB, curRGB); + re->shaderRGBA[0] = 0xff * alpha * curRGB[0]; + re->shaderRGBA[1] = 0xff * alpha * curRGB[1]; + re->shaderRGBA[2] = 0xff * alpha * curRGB[2]; + re->shaderRGBA[3] = 0xff; + + re->reType = RT_SPRITE; + + trap_R_AddRefEntityToScene( re ); +} + + +/* +=================== +CG_AddBezier + +For trek, for the imod and the...uh...imod. + +=================== +*/ +void CG_AddBezier( localEntity_t *le ) +{ + refEntity_t *re; + float frac, alpha; + float t = (cg.time - le->startTime)*0.001; // time elapsed since beginning of effect + vec3_t vTempPos; + + re = &le->refEntity; + + frac = (cg.time - le->startTime) / ( float ) ( le->endTime - le->startTime ); + if ( frac > 1 ) + frac = 1.0; // can happen during connection problems + else if (frac < 0) + frac = 0.0; + + // Use the liferate to set the scale over time. + re->data.bezier.width = le->data.line.width + (le->data.line.dwidth * frac); + if (re->data.bezier.width <= 0) + { + CG_FreeLocalEntity( le ); + return; + } + + // We will assume here that we want additive transparency effects. + alpha = le->alpha + (le->dalpha * frac); + re->shaderRGBA[0] = 0xff * alpha; + re->shaderRGBA[1] = 0xff * alpha; + re->shaderRGBA[2] = 0xff * alpha; + re->shaderRGBA[3] = 0xff; // Yes, we could apply c to this too, but fading the color is better for lines. + + re->reType = RT_BEZIER; + + // the refEntity only stores the two control points, so we need to update them here with + //the control_velocity and control_acceleration, then store the results in refEntity. + //use (cg.time - le->startTime) as a value for elapsed time t, plug it into the position formula: + // + // x = x0 + (v0 * t) + (0.5 * a * t * t) + // + //...where x is the position at time t, x0 is initial control point position, v0 is control point velocity, + //and a is control point acceleration + // + + // update control point 1 + VectorMA(le->data.line.control1, t, le->data.line.control1_velocity, vTempPos); + VectorMA(vTempPos, (0.5*t*t), le->data.line.control1_acceleration, re->data.bezier.control1); + + // update control point 2 + VectorMA(le->data.line.control2, t, le->data.line.control2_velocity, vTempPos); + VectorMA(vTempPos, (0.5*t*t), le->data.line.control2_acceleration, re->data.bezier.control2); + + trap_R_AddRefEntityToScene( re ); +} + + + +/* +=================== +CG_AddCylinder + +For trek, cylinder primitive. +=================== +*/ +void CG_AddCylinder( localEntity_t *le ) +{ + refEntity_t *re; + float frac, alpha; + + re = &le->refEntity; + + frac = ( cg.time - le->startTime ) / ( float ) ( le->endTime - le->startTime ); + if ( frac > 1 ) + frac = 1.0; // can happen during connection problems + else if (frac < 0) + frac = 0.0; + + // Use the liferate to set the scale over time. + re->data.cylinder.height = le->data.cylinder.height + (le->data.cylinder.dheight*frac); + if (re->data.cylinder.height <= 0) + { + CG_FreeLocalEntity( le ); + return; + } + re->data.cylinder.width = le->data.cylinder.width + (le->data.cylinder.dwidth*frac); + if (re->data.cylinder.width <= 0) + { + CG_FreeLocalEntity( le ); + return; + } + re->data.cylinder.width2 = le->data.cylinder.width2 + (le->data.cylinder.dwidth2*frac); + if (re->data.cylinder.width2 <= 0) + { + CG_FreeLocalEntity( le ); + return; + } + + // Calculate the current alpha. + alpha = le->alpha + (le->dalpha * frac); + re->shaderRGBA[0] = 0xff * alpha; + re->shaderRGBA[1] = 0xff * alpha; + re->shaderRGBA[2] = 0xff * alpha; + re->shaderRGBA[3] = 0xff; + + re->reType = RT_CYLINDER; + + trap_R_AddRefEntityToScene( re ); +} + + + +/* +=================== +CG_AddElectricity + +For trek, electricity primitive. +=================== +*/ + +#define DEFAULT_DEVIATION 0.5 + +void CG_AddElectricity( localEntity_t *le ) { + refEntity_t *re; + float frac, alpha; + + re = &le->refEntity; + + frac = ( cg.time - le->startTime ) / ( float ) ( le->endTime - le->startTime ); + if ( frac > 1 ) + frac = 1.0; // can happen during connection problems + else if (frac < 0) + frac = 0.0; + + re->data.electricity.width = le->data.electricity.width + (le->data.electricity.dwidth*frac); + + // Calculate the current alpha. + alpha = le->alpha + (le->dalpha * frac); + re->shaderRGBA[0] = 0xff * alpha; + re->shaderRGBA[1] = 0xff * alpha; + re->shaderRGBA[2] = 0xff * alpha; + re->shaderRGBA[3] = 0xff; + + re->reType = RT_ELECTRICITY; + + trap_R_AddRefEntityToScene( re ); +} + +/* +=================== +CG_AddParticle + +For trek, special explosion stuff sometimes wants these +=================== +*/ +static void CG_AddParticle( localEntity_t *le ) +{ + refEntity_t *re; + float frac, alpha; + vec3_t delta, dir; + float len; + trace_t trace; + + re = &le->refEntity; + + //safety check - since this renders over all, make sure we can't see this thru a wall + if ( re->renderfx & RF_DEPTHHACK ) { + CG_Trace( &trace, re->origin, NULL, NULL, cg.refdef.vieworg, cg.predictedPlayerState.clientNum, MASK_SOLID ); + + if ( trace.fraction != 1.0 ) + return; + + } + + frac = ( cg.time - le->startTime ) / ( float ) ( le->endTime - le->startTime ); + if ( le->leFlags & LEF_SINE_SCALE ) { + //frac = 1.0-(0.5f * sin( 4.0f * frac + 0.75f ) + 0.5f); //TiM: Sine calc //+ 1.5f + frac = 1.0-(0.65 * sin( 3.0 * frac +0.75 ) + 0.35); + } + + if ( frac > 1 ) + frac = 1.0; // can happen during connection problems + else if (frac < 0) + frac = 0.0; + + //CG_Printf( "%f\n", frac ); + + // Use the liferate to set the scale over time. + if ( !(le->leFlags & LEF_REVERSE_SCALE) ) + re->data.sprite.radius = le->data.particle.radius + (le->data.particle.dradius*frac); + else + re->data.sprite.radius = le->data.particle.radius - (le->data.particle.dradius*frac); + if (re->data.sprite.radius <= 0) + { + CG_FreeLocalEntity( le ); + return; + } + + if (le->leFlags & LEF_MOVE) + { + // kef -- do these two lines _before_ copying origin into oldorigin + VectorSubtract(re->oldorigin, re->origin, dir); + VectorNormalize(dir); + + VectorCopy(re->origin, re->oldorigin); + BG_EvaluateTrajectory( &le->pos, cg.time, re->origin ); + + if (le->leFlags & LEF_USE_COLLISION) + { + // trace a line from previous position to new position + CG_Trace( &trace, re->oldorigin, NULL, NULL, re->origin, -1, CONTENTS_SOLID ); + + if ( trace.fraction != 1.0 ) + { // Hit something. + // 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; + } + + // reflect the velocity on the trace plane + CG_ReflectVelocity( le, &trace ); + } + VectorSubtract(re->oldorigin, re->origin, dir); + VectorNormalize(dir); + } + } + + // 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->data.particle.radius ) { + CG_FreeLocalEntity( le ); + return; + } + + // kef -- here's where I, in my infinite wisdom, have decided to emulate the singleplayer + //particle think function + VectorNegate(dir, le->data.particle.dir); + if (le->data.particle.thinkFn) + { + le->data.particle.thinkFn(le); + } + + // Calculate the current alpha. + alpha = le->alpha + (le->dalpha * frac); + re->shaderRGBA[0] = 0xff * alpha; + re->shaderRGBA[1] = 0xff * alpha; + re->shaderRGBA[2] = 0xff * alpha; + re->shaderRGBA[3] = 0xff; + + re->reType = RT_SPRITE; + + trap_R_AddRefEntityToScene( re ); +} + +static void CG_AddSpawner( localEntity_t *le ) +{ + refEntity_t *re; + vec3_t dir; + trace_t trace; + + re = &le->refEntity; + if (le->leFlags & LEF_MOVE) + { + // kef -- do these two lines _before_ copying origin into oldorigin + VectorSubtract(re->oldorigin, re->origin, dir); + VectorNormalize(dir); + + VectorCopy(re->origin, re->oldorigin); + BG_EvaluateTrajectory( &le->pos, cg.time, re->origin ); + + if (le->leFlags & LEF_USE_COLLISION) + { + // trace a line from previous position to new position + CG_Trace( &trace, re->oldorigin, NULL, NULL, re->origin, -1, CONTENTS_SOLID ); + + if ( trace.fraction != 1.0 ) + { // Hit something. + // 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; + } + + // reflect the velocity on the trace plane + CG_ReflectVelocity( le, &trace ); + } + VectorSubtract(re->oldorigin, re->origin, dir); + VectorNormalize(dir); + } + } + + // kef -- here's where I, in my infinite wisdom, have decided to emulate the singleplayer + //particle think function + if (cg.time < le->data.spawner.nextthink) + { + return; + } + le->data.spawner.nextthink = cg.time + (le->data.spawner.delay + + (le->data.spawner.delay*flrandom(-le->data.spawner.variance,le->data.spawner.variance))); + + if (le->data.spawner.thinkFn) + { + le->data.spawner.thinkFn(le); + } + if (le->data.spawner.dontDie) + { + le->endTime = le->endTime + 10000; + } +} + + +/* +================ +CG_AddFragment +================ +*/ +void CG_AddFragment( localEntity_t *le ) { + vec3_t newOrigin; + trace_t trace; + int k; + + 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, 0 /*le->ownerGentNum*/, 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 ); + for(k = 0; k < 3; k++) + { + VectorScale(le->refEntity.axis[k], le->data.fragment.radius, le->refEntity.axis[k]); + } + + } + + 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; + } + + // reflect the velocity on the trace plane + CG_ReflectVelocity( le, &trace ); + //FIXME: if LEF_TUMBLE, change avelocity too? + + trap_R_AddRefEntityToScene( &le->refEntity ); +} + + +//============================================================================== + +/* +=================== +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; + } + + if (le->leFlags & LEF_ONE_FRAME) + { // If this flag is set, only render one single frame, no more. + if (le->leFlags & LEF_ONE_FRAME_DONE) + { + CG_FreeLocalEntity( le ); + continue; + } + else + { + le->leFlags |= LEF_ONE_FRAME_DONE; + } + } + + switch ( le->leType ) { + default: + CG_Error( "Bad leType: %i", le->leType ); + break; + + case LE_MARK: + break; + + case LE_SPRITE_EXPLOSION: + CG_AddSpriteExplosion( le ); + break; + + case LE_EXPLOSION: + CG_AddExplosion( le ); + break; + + case LE_MOVE_SCALE_FADE: // water bubbles + CG_AddMoveScaleFade( le ); + break; + + case LE_FADE_RGB: // teleporters, railtrails + CG_AddFadeRGB( le ); + break; + + case LE_FALL_SCALE_FADE: // gib blood trails + CG_AddFallScaleFade( le ); + break; + + case LE_SCALE_FADE: // rocket trails + CG_AddScaleFade( le ); + break; + + case LE_SCALE_FADE_SPRITE: // Trek type for oriented poly sprites. + CG_AddScaleFadeSprite( le ); + break; + + case LE_LINE: // Trek type for beams. + CG_AddLine( le ); + break; + + case LE_LINE2: // Trek type for beams, with taper support. + CG_AddLine2( le ); + break; + + case LE_OLINE: // Trek type for rectangles + CG_AddOLine( le ); + break; + + case LE_TRAIL: // Trek type for sparks. + CG_AddTrail( le ); + break; + + case LE_VIEWSPRITE: // Trek primitive for camera-facing sprites. + CG_AddViewSprite( le ); + break; + + case LE_BEZIER: + CG_AddBezier( le ); + break; + + case LE_QUAD: + CG_AddQuad( le ); + break; + + case LE_CYLINDER: + CG_AddCylinder(le); + break; + + case LE_ELECTRICITY: + CG_AddElectricity(le); + break; + + case LE_PARTICLE: + CG_AddParticle(le); + break; + + case LE_SPAWNER: + CG_AddSpawner(le); + break; + + case LE_FRAGMENT: + CG_AddFragment(le); + break; + } + } +} + + + + diff --git a/cgame/cg_main.c b/cgame/cg_main.c new file mode 100644 index 0000000..5d8ebc4 --- /dev/null +++ b/cgame/cg_main.c @@ -0,0 +1,2333 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_main.c -- initialization and primary entry point for cgame + +//TiM : 22/12/2005 - Commented out any assets that we no longer use. +//TiM : 24/12/2005 - Added a ranks parsing function. + +#include "cg_local.h" +#include "cg_text.h" + +void CG_Init( int serverMessageNum, int serverCommandSequence ); +void CG_Shutdown( void ); +void CG_LoadIngameText(void); +void CG_LoadObjectivesForMap(void); +void BG_LoadItemNames(void); +qboolean CG_LoadUsablesStrings( void ); + +//TiM - Placed the func @ the bottom of the page for easier access +//extern static qboolean CG_LoadRanks( void ); + +extern void FX_InitSinTable(void); + +//extern lensReflec_s lensReflec[10]; + +int cg_liftEnts[MAX_CLIENTS]; +int cg_numAnims; +int cg_numSndAnims; + +animsSndList_t cg_animsSndList[MAX_CLIENTS]; + +animsList_t cg_animsList[MAX_CLIENTS]; + +/* +================ +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 ) { + switch ( command ) { + case CG_INIT: + CG_Init( arg0, arg1 ); + 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(); + default: + CG_Error( "vmMain: unknown command %i", command ); + break; + } + return -1; +} + + +cg_t cg; +cgs_t cgs; +centity_t cg_entities[MAX_GENTITIES]; +weaponInfo_t cg_weapons[MAX_WEAPONS]; +itemInfo_t cg_items[MAX_ITEMS]; + +vmCvar_t cg_centertime; +vmCvar_t cg_runpitch; +vmCvar_t cg_runroll; +vmCvar_t cg_bobup; +vmCvar_t cg_bobpitch; +vmCvar_t cg_bobroll; +vmCvar_t cg_swingSpeed; +vmCvar_t cg_shadows; +vmCvar_t cg_gibs; +vmCvar_t cg_drawTimer; +vmCvar_t cg_drawFPS; +vmCvar_t cg_drawSnapshot; +vmCvar_t cg_draw3dIcons; +vmCvar_t cg_drawIcons; +vmCvar_t cg_drawAmmoWarning; +vmCvar_t cg_drawCrosshair; +vmCvar_t cg_drawCrosshairNames; +vmCvar_t cg_dynamicCrosshairNames; +vmCvar_t cg_drawRewards; +vmCvar_t cg_crosshairSize; +vmCvar_t cg_crosshairX; +vmCvar_t cg_crosshairY; +vmCvar_t cg_crosshairHealth; +vmCvar_t cg_draw2D; +vmCvar_t cg_drawStatus; +vmCvar_t cg_animSpeed; +vmCvar_t cg_debugAnim; +vmCvar_t cg_debugPosition; +vmCvar_t cg_debugEvents; +vmCvar_t cg_errorDecay; +vmCvar_t cg_nopredict; +vmCvar_t cg_noPlayerAnims; +vmCvar_t cg_showmiss; +vmCvar_t cg_footsteps; +vmCvar_t cg_addMarks; +vmCvar_t cg_viewsize; +vmCvar_t cg_drawGun; +vmCvar_t cg_gun_frame; +vmCvar_t cg_gun_x; +vmCvar_t cg_gun_y; +vmCvar_t cg_gun_z; +vmCvar_t cg_autoswitch; +vmCvar_t cg_ignore; +vmCvar_t cg_simpleItems; +vmCvar_t cg_fov; +vmCvar_t cg_zoomFov; +vmCvar_t cg_thirdPerson; +vmCvar_t cg_thirdPersonRange; +vmCvar_t cg_thirdPersonAngle; +//RPG-X: TiM - Cool JKA CVARs +vmCvar_t cg_thirdPersonVertOffset; +vmCvar_t cg_thirdPersonHorzOffset; +vmCvar_t cg_thirdPersonAlpha; +vmCvar_t cg_thirdPersonCameraDamp; +vmCvar_t cg_thirdPersonTargetDamp; +vmCvar_t cg_thirdPersonPitchOffset; +vmCvar_t cg_stereoSeparation; +vmCvar_t cg_lagometer; +vmCvar_t cg_drawAttacker; +vmCvar_t cg_synchronousClients; +vmCvar_t cg_teamChatTime; +vmCvar_t cg_teamChatHeight; +vmCvar_t cg_stats; +vmCvar_t cg_reportDamage; +vmCvar_t cg_buildScript; +vmCvar_t cg_forceModel; +vmCvar_t cg_paused; +vmCvar_t cg_blood; + +vmCvar_t cg_predictItems; +vmCvar_t cg_deferPlayers; +vmCvar_t cg_drawTeamOverlay; +vmCvar_t cg_teamOverlayUserinfo; +vmCvar_t ui_playerClass; +vmCvar_t ui_playerRank; +vmCvar_t cg_disablekillmsgs; +vmCvar_t cg_drawradar; +vmCvar_t rpg_ctribgrenade; //RPG-X: - RedTechie i think J2J added this to control invisible tripmines on clients end - TOFIX:THIS ALSO COULD BE HACKED! +vmCvar_t cg_dynamicCrosshair; //RPG-X | Phenix | 09/06/2005 +vmCvar_t doomHead; //RPG-X | Phenix | 09/06/2005 +vmCvar_t cg_dynamiclensflares; +vmCvar_t cg_noTalkingHeads; +vmCvar_t cg_noDynamicRanks; + +vmCvar_t noAdminChat; //TiM + +//RPG-X: TiM - Player Model Parameters +vmCvar_t pms_age; +vmCvar_t pms_height; +vmCvar_t pms_weight; +vmCvar_t pms_race; + +vmCvar_t cg_defaultChar; + +//RPG-X: TiM - Emote system model offset +vmCvar_t emote_Offset; + +vmCvar_t cg_thirdPersonZoomRate; +vmCvar_t cg_noFrowningHeads; +vmCvar_t cg_noBlinkingHeads; + +//RPG-X: TiM - First Person Body +vmCvar_t cg_firstPersonBody; + +vmCvar_t cg_defaultModel; + +vmCvar_t cg_showEntityNums; + +//TiM - SecurityCode +vmCvar_t sv_securityHash; +vmCvar_t sv_securityCode; + +//TiM - widescreen +vmCvar_t cg_handleWidescreen; +vmCvar_t ui_handleWidescreen; + +//vmCvar_t cg_chatBGColor; + +vmCvar_t cg_chatColor; + +//RPG-X | GSIO01 | 11/05/2009 +vmCvar_t rpg_forceFieldSet; + +// grp cvars +vmCvar_t grp_berp; + + +//RPG-X | Phenix | 05/02/2006 +//Ban System (and it's backup cvars) +//vmCvar_t cg_playerID; +//vmCvar_t s_mhz; //Part A + 562 +//vmCvar_t cg_fow; //Part B + 333 +//vmCvar_t cl_avgPacket; //Part C + 99 +//vmCvar_t cg_rewardsSize;//Part D + 120 + +typedef struct { + vmCvar_t *vmCvar; + char *cvarName; + char *defaultString; + int cvarFlags; +} cvarTable_t; + +static cvarTable_t cvarTable[] = { + { &cg_ignore, "cg_ignore", "0", 0 }, // used for debugging + //{ &s_mhz, "s_mhz", "0", CVAR_ARCHIVE | CVAR_ROM | CVAR_NORESTART }, //RPG-X | Phenix | 05/02/2006 + { &cg_autoswitch, "cg_autoswitch", "1", CVAR_ARCHIVE }, + { &cg_drawGun, "cg_drawGun", "1", CVAR_ARCHIVE }, + { &cg_zoomFov, "cg_zoomfov", "22.5", CVAR_ARCHIVE }, + { &cg_fov, "cg_fov", "80", CVAR_ARCHIVE }, + //{ &cg_fow, "cg_fow", "0", CVAR_ARCHIVE | CVAR_ROM | CVAR_NORESTART }, //RPG-X | Phenix | 05/02/2006 + { &cg_viewsize, "cg_viewsize", "100", CVAR_ARCHIVE }, + { &cg_stereoSeparation, "cg_stereoSeparation", "0.4", CVAR_ARCHIVE }, + //{ &s_mhz, "s_mhz", "0", CVAR_ARCHIVE | CVAR_ROM | CVAR_NORESTART }, //RPG-X | Phenix | 05/02/2006 + { &cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE }, + { &cg_gibs, "cg_gibs", "0", CVAR_ARCHIVE }, //no gibs in trek + { &cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE }, + { &cg_drawStatus, "cg_drawStatus", "1", CVAR_ARCHIVE }, + { &cg_drawTimer, "cg_drawTimer", "0", CVAR_ARCHIVE }, + { &cg_drawFPS, "cg_drawFPS", "0", CVAR_ARCHIVE }, + { &cg_drawSnapshot, "cg_drawSnapshot", "0", CVAR_ARCHIVE }, + { &cg_draw3dIcons, "cg_draw3dIcons", "1", CVAR_ARCHIVE }, + { &cg_drawIcons, "cg_drawIcons", "1", CVAR_ARCHIVE }, + { &cg_drawAmmoWarning, "cg_drawAmmoWarning", "1", CVAR_ARCHIVE }, + { &cg_drawAttacker, "cg_drawAttacker", "0", CVAR_ARCHIVE }, //RPG-X TiM + { &cg_drawCrosshair, "cg_drawCrosshair", "1", CVAR_ARCHIVE }, + { &cg_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE }, + { &cg_drawRewards, "cg_drawRewards", "1", CVAR_ARCHIVE }, + //{ &cg_rewardsSize, "cg_rewardsSize", "0", CVAR_ARCHIVE | CVAR_ROM | CVAR_NORESTART }, //RPG-X | Phenix | 05/02/2006 + { &cg_crosshairSize, "cg_crosshairSize", "24", CVAR_ARCHIVE }, + { &cg_crosshairHealth, "cg_crosshairHealth", "1", CVAR_ARCHIVE }, + { &cg_crosshairX, "cg_crosshairX", "0", CVAR_ARCHIVE }, + { &cg_crosshairY, "cg_crosshairY", "0", CVAR_ARCHIVE }, + { &cg_simpleItems, "cg_simpleItems", "0", CVAR_ARCHIVE }, + { &cg_addMarks, "cg_marks", "1", 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", "3", CVAR_CHEAT }, + { &cg_runpitch, "cg_runpitch", "0.002", CVAR_ARCHIVE}, + { &cg_runroll, "cg_runroll", "0.005", CVAR_ARCHIVE }, + { &cg_bobup , "cg_bobup", "0.005", CVAR_ARCHIVE }, + { &cg_bobpitch, "cg_bobpitch", "0.002", CVAR_ARCHIVE }, + { &cg_bobroll, "cg_bobroll", "0.002", CVAR_ARCHIVE }, + { &cg_swingSpeed, "cg_swingSpeed", "0.2", CVAR_CHEAT }, //0.3 //TiM - slowed for better realism + { &cg_animSpeed, "cg_animspeed", "1", CVAR_CHEAT }, + { &cg_debugAnim, "cg_debuganim", "0", CVAR_CHEAT }, + { &cg_debugPosition, "cg_debugposition", "0", CVAR_CHEAT }, + { &cg_debugEvents, "cg_debugevents", "0", CVAR_CHEAT }, + { &cg_errorDecay, "cg_errordecay", "100", 0 }, + { &cg_nopredict, "cg_nopredict", "0", 0 }, + { &cg_noPlayerAnims, "cg_noplayeranims", "0", CVAR_CHEAT }, + { &cg_showmiss, "cg_showmiss", "0", 0 }, + { &cg_footsteps, "cg_footsteps", "1", CVAR_CHEAT }, + { &cg_thirdPersonRange, "cg_thirdPersonRange", "80", CVAR_ARCHIVE }, + { &cg_thirdPersonAngle, "cg_thirdPersonAngle", "0", CVAR_ARCHIVE }, + { &cg_thirdPerson, "cg_thirdPerson", "0", CVAR_ARCHIVE }, + { &cg_thirdPersonVertOffset, "cg_thirdPersonVertOffset", "16", CVAR_ARCHIVE }, //RPG-X: TiM + { &cg_thirdPersonHorzOffset, "cg_thirdPersonHorzOffset", "0", CVAR_ARCHIVE }, //RPG-X: TiM + { &cg_thirdPersonAlpha, "cg_thirdPersonAlpha", "1.0", CVAR_ARCHIVE },//RPG-X: TiM + { &cg_thirdPersonCameraDamp, "cg_thirdPersonCameraDamp", "0.3", CVAR_ARCHIVE },//RPG-X: TiM + { &cg_thirdPersonTargetDamp, "cg_thirdPersonTargetDamp", "0.5", CVAR_ARCHIVE },//RPG-X: TiM + { &cg_thirdPersonPitchOffset, "cg_thirdPersonPitchOffset", "0.0", CVAR_ARCHIVE},//RPG-X: TiM + { &cg_firstPersonBody, "cg_firstPersonBody", "0", CVAR_ARCHIVE}, + { &cg_teamChatTime, "cg_teamChatTime", "3000", CVAR_ARCHIVE }, + { &cg_teamChatHeight, "cg_teamChatHeight", "0", CVAR_ARCHIVE }, + { &cg_forceModel, "cg_forceModel", "0", CVAR_ARCHIVE }, + { &cg_predictItems, "cg_predictItems", "1", CVAR_ARCHIVE }, + { &cg_deferPlayers, "cg_deferPlayers", "1", CVAR_ARCHIVE }, + { &cg_drawTeamOverlay, "cg_drawTeamOverlay", "0", CVAR_ARCHIVE }, + { &cg_teamOverlayUserinfo, "teamoverlay", "0", CVAR_ROM | CVAR_USERINFO }, + { &cg_stats, "cg_stats", "0", 0 }, + { &cg_reportDamage, "cg_reportDamage", "0", 0}, + { &rpg_ctribgrenade, "rpg_ctribgrenade", "0", CVAR_ARCHIVE}, //RPG-X: - RedTechie i think J2J added this to control invisible tripmines on clients end - TOFIX:THIS ALSO COULD BE HACKED! + { &pms_age, "age", "Unknown", CVAR_ARCHIVE | CVAR_USERINFO }, + { &pms_height, "height", "1.0", CVAR_ARCHIVE | CVAR_USERINFO }, + { &pms_weight, "weight", "1.0", CVAR_ARCHIVE | CVAR_USERINFO }, + { &pms_race, "race", "Unknown", CVAR_ARCHIVE | CVAR_USERINFO }, + { &emote_Offset, "modelOffset", "0", CVAR_ARCHIVE | CVAR_USERINFO }, + + { &cg_defaultChar, "cg_defaultChar", DEFAULT_CHAR, CVAR_ARCHIVE }, + + // the following variables are created in other parts of the system, + // but we also reference them here + + { &cg_buildScript, "com_buildScript", "0", 0 }, // force loading of all possible data amd error on failures + { &cg_paused, "cl_paused", "0", CVAR_ROM }, + { &cg_blood, "com_blood", "0", CVAR_ARCHIVE }, //no blood in trek + //{ &cl_avgPacket, "cl_avgPacket", "0", CVAR_ARCHIVE | CVAR_ROM | CVAR_NORESTART }, //RPG-X | Phenix | 05/02/2006 + { &cg_synchronousClients, "g_synchronousClients", "0", 0 }, // communicated by systeminfo + { &ui_playerClass, "ui_playerClass", "noclass", CVAR_ARCHIVE /*| CVAR_ROM | CVAR_USERINFO*/ }, + //{ &ui_playerclass, "ui_playerclass", "0", 0 }, // player class + { &ui_playerRank, "ui_playerRank", "crewman", CVAR_ARCHIVE /*| CVAR_ROM | CVAR_USERINFO*/ }, + + { &cg_disablekillmsgs, "cg_disablekillmsgs", "0", CVAR_ARCHIVE }, + { &cg_drawradar, "cg_drawradar", "1", CVAR_ARCHIVE }, + + { &cg_dynamicCrosshair, "cg_dynamicCrosshair", "1", CVAR_ARCHIVE }, //RPG-X | Phenix | 09/06/2005 + { &doomHead, "doomHead", "0", CVAR_ARCHIVE }, //RPG-X | Phenix | 09/06/2005 + { &cg_dynamiclensflares, "cg_dynamicLensFlares", "1", CVAR_ARCHIVE }, //RPG-X | TiM | 29/6/2005 + + { &cg_dynamicCrosshairNames, "cg_dynamicCrosshairNames", "1", CVAR_ARCHIVE }, + + { &noAdminChat, "noAdminChat", "0", CVAR_ARCHIVE | CVAR_USERINFO }, + + //RPG-X Memory optimization CVARs + { &cg_noTalkingHeads, "cg_noTalkingHeads", "0", CVAR_ARCHIVE }, + { &cg_noDynamicRanks, "cg_noDynamicRanks", "0", CVAR_ARCHIVE }, + { &cg_noFrowningHeads, "cg_noFrowningHeads", "1", CVAR_ARCHIVE }, //On by default since this isn't REALLY needed... :P + { &cg_noBlinkingHeads, "cg_noBlinkingHeads", "0", CVAR_ARCHIVE }, + + { &cg_thirdPersonZoomRate, "cg_thirdPersonZoomRate", "25", CVAR_ARCHIVE }, + { &cg_showEntityNums, "cg_showEntityNums", "1", CVAR_ARCHIVE }, + + //TiM - security cvars + { &sv_securityHash, "sv_securityHash", "4294967295", CVAR_ARCHIVE | CVAR_ROM | CVAR_NORESTART }, + { &sv_securityCode, "sv_securityCode", "4294967295", CVAR_ARCHIVE | CVAR_USERINFO | CVAR_ROM | CVAR_NORESTART }, + + { &cg_handleWidescreen, "cg_handleWidescreen", "1", CVAR_ARCHIVE }, + { &ui_handleWidescreen, "ui_handleWidescreen", "1", CVAR_ARCHIVE }, + + { &cg_chatColor, "cg_chatColor", "", CVAR_ARCHIVE }, + + //RPG-X | GSIO01 | 11/05/2009 + { &rpg_forceFieldSet, "rpg_forceFieldSet", "1", CVAR_ARCHIVE | CVAR_LATCH }, + + //{ &cg_chatBGColor, "cg_chatBGColor", "", CVAR_ARCHIVE } + + //{ &cg_defaultModel, "cg_defaultModel", DEFAULT_PLAYER, CVAR_ARCHIVE }, + + //{ &cg_playerID, "cg_playerID", "0", CVAR_ARCHIVE | CVAR_ROM | CVAR_NORESTART } + + // grp cvars + { &grp_berp, "grp_berp", "0", CVAR_ARCHIVE | CVAR_LATCH } +}; + +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 ); + } + + // see if we are also running the server on this machine + trap_Cvar_VariableStringBuffer( "sv_running", var, sizeof( var ) ); + cgs.localServer = atoi( var ); +} + + +/* +================= +CG_UpdateCvars +================= +*/ +void CG_UpdateCvars( void ) { + int i; + cvarTable_t *cv; + + for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) + { + trap_Cvar_Update( cv->vmCvar ); + } + + // check for modications here + + // If team overlay is on, ask for updates from the server. If its off, + // let the server know so we don't receive it + if ( drawTeamOverlayModificationCount != cg_drawTeamOverlay.modificationCount ) { + drawTeamOverlayModificationCount = cg_drawTeamOverlay.modificationCount; + + if ( cg_drawTeamOverlay.integer > 0 ) { + trap_Cvar_Set( "teamoverlay", "1" ); + } else { + trap_Cvar_Set( "teamoverlay", "0" ); + } + } +} + + +int CG_CrosshairPlayer( void ) { + if ( cg.time > ( cg.crosshairClientTime + 1000 ) ) { + return -1; + } + return cg.crosshairClientNum; +} + + +int CG_LastAttacker( void ) { + if ( !cg.attackerTime ) { + return -1; + } + return cg.snap->ps.persistant[PERS_ATTACKER]; +} + + +void QDECL CG_Printf( const char *msg, ... ) { + va_list argptr; + char text[1024]; + char *msgPtr; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + if ( cg_chatColor.integer > 0 && cg_chatColor.integer < 8 ) + { + msgPtr = text; + + while ( msgPtr && *msgPtr != '\0' ) + { + if ( *msgPtr == '^' ) + { + msgPtr++; + *msgPtr = 48 + cg_chatColor.integer; + } + + msgPtr++; + } + } + + //CVAR background + /*if ( cg_chatBGColor.string[0] ) + { + char *bgColor; + char *rimColor; + int rgb[3]; + int i; + + Q_strlwr( cg_chatBGColor.string ); + + bgColor = cg_chatBGColor.string; + + if ( *bgColor == '#' ) + { + bgColor[7]='\0'; + bgColor++; + + for ( i=0; i < 3; i++ ) + { + if ( *bgColor >= 'a' && *bgColor <='f' ) + rgb[i] = 16 * ( 10 + (int)(*bgColor - 'a')); + else + rgb[i] = 16 * atoi(bgColor[0]); + + bgColor++; + + if ( *bgColor >= 'a' && *bgColor <='f' ) + rgb[i] += ( 10 + (int)(*bgColor - 'a')); + else + rgb[i] += atoi(bgColor[0]); + } + + + } + }*/ + + trap_Print( text ); +} + +void QDECL CG_Error( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + trap_Error( text ); +} + +#ifndef CGAME_HARD_LINKED +// this is only here so the functions in q_shared.c and bg_*.c can link (FIXME) + +void QDECL Com_Error( int level, const char *error, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, error); + vsprintf (text, error, argptr); + va_end (argptr); + + CG_Error( "%s", text); +} + +void QDECL Com_Printf( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + CG_Printf ("%s", text); +} + +#endif + + + +/* +================ +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) { + CG_Error( "PrecacheItem: %s has bad precache string", + item->classname); + return; + } + memcpy (data, start, len); + data[len] = 0; + if ( *s ) { + s++; + } + + if ( !strcmp(data+len-3, "wav" )) { + trap_S_RegisterSound( data ); + } + } +} + + +/* +================= +CG_RegisterSounds + +called during a precache command +================= +*/ +static void CG_RegisterSounds( void ) +{ + int i; + char items[MAX_ITEMS+1]; + char name[MAX_QPATH]; + const char *soundName; + + cg.loadLCARSStage = 1; // Loading bar stage 1 + CG_LoadingString( "sounds" ); + + //TiM + /*if ( cgs.timelimit || cg_buildScript.integer ) { // should we always load this? + cgs.media.oneMinuteSound = trap_S_RegisterSound( "sound/voice/computer/misc/1_minute.wav" ); + cgs.media.fiveMinuteSound = trap_S_RegisterSound( "sound/voice/computer/misc/5_minute.wav" ); + cgs.media.suddenDeathSound = trap_S_RegisterSound( "sound/voice/computer/misc/sudden_death.wav" ); + }*/ + + //TiM + /*if ( cgs.fraglimit || cg_buildScript.integer ) { + cgs.media.oneFragSound = trap_S_RegisterSound( "sound/voice/computer/misc/1_frag.wav" ); + cgs.media.twoFragSound = trap_S_RegisterSound( "sound/voice/computer/misc/2_frags.wav" ); + cgs.media.threeFragSound = trap_S_RegisterSound( "sound/voice/computer/misc/3_frags.wav" ); + }*/ + +// if ( cgs.gametype == GT_TOURNAMENT || cg_buildScript.integer ) { +// We always need this since a warmup can be enabled in any game mode +//TiM /*cgs.media.count3Sound = trap_S_RegisterSound( "sound/voice/computer/misc/three.wav" ); + cgs.media.count2Sound = trap_S_RegisterSound( "sound/voice/computer/misc/two.wav" ); + cgs.media.count1Sound = trap_S_RegisterSound( "sound/voice/computer/misc/one.wav" ); + cgs.media.countFightSound = trap_S_RegisterSound( "sound/voice/computer/misc/fight.wav" ); + cgs.media.countPrepareSound = trap_S_RegisterSound( "sound/voice/computer/misc/prepare.wav" ); +// } + //TiM +/* if ( cgs.gametype >= GT_TEAM || cg_buildScript.integer ) { + cgs.media.redLeadsSound = trap_S_RegisterSound( "sound/voice/computer/misc/redleads.wav" ); + cgs.media.blueLeadsSound = trap_S_RegisterSound( "sound/voice/computer/misc/blueleads.wav" ); + cgs.media.teamsTiedSound = trap_S_RegisterSound( "sound/voice/computer/misc/teamstied.wav" ); + cgs.media.hitTeamSound = trap_S_RegisterSound( "sound/feedback/hit_teammate.wav" ); + }*/ + + //TiM + /*if (cgs.gametype == GT_CTF || cg_buildScript.integer) + { + cgs.media.ctfStealSound = trap_S_RegisterSound("sound/voice/computer/misc/flagtk_blu.wav"); + cgs.media.ctfReturnSound = trap_S_RegisterSound("sound/voice/computer/misc/flagret_blu.wav"); + cgs.media.ctfScoreSound = trap_S_RegisterSound("sound/voice/computer/misc/flagcap_blu.wav"); + cgs.media.ctfYouStealVoiceSound = trap_S_RegisterSound("sound/voice/computer/misc/stolen.wav"); + cgs.media.ctfYouDroppedVoiceSound = trap_S_RegisterSound("sound/voice/computer/misc/dropped_e.wav"); + cgs.media.ctfYouReturnVoiceSound = trap_S_RegisterSound("sound/voice/computer/misc/returned.wav"); + cgs.media.ctfYouScoreVoiceSound = trap_S_RegisterSound("sound/voice/computer/misc/scored.wav"); + cgs.media.ctfTheyStealVoiceSound = trap_S_RegisterSound("sound/voice/computer/misc/stolen_e.wav"); + cgs.media.ctfTheyDroppedVoiceSound = trap_S_RegisterSound("sound/voice/computer/misc/dropped.wav"); // Note the flip, because YOU dropped THEIR flag + cgs.media.ctfTheyReturnVoiceSound = trap_S_RegisterSound("sound/voice/computer/misc/returned_e.wav"); + cgs.media.ctfTheyScoreVoiceSound = trap_S_RegisterSound("sound/voice/computer/misc/scored_e.wav"); + }*/ + + cgs.media.interfaceSnd1 = trap_S_RegisterSound( "sound/interface/button4.wav" ); + + //cgs.media.selectSound = trap_S_RegisterSound( "sound/silence.wav" );//trap_S_RegisterSound( "sound/weapons/change.wav" ); + //cgs.media.wearOffSound = trap_S_RegisterSound( "sound/items/wearoff.wav" ); + cgs.media.useNothingSound = trap_S_RegisterSound( "sound/items/use_nothing.wav" ); + + //TiM//cgs.media.holoOpenSound = trap_S_RegisterSound( "sound/movers/doors/holoopen.wav" ); + cgs.media.teleInSound = trap_S_RegisterSound( "sound/world/transin.wav" ); + //cgs.media.teleOutSound = trap_S_RegisterSound( "sound/world/transout.wav" ); + cgs.media.transportSound = trap_S_RegisterSound( "sound/world/transporter.wav" ); + cgs.media.respawnSound = trap_S_RegisterSound( "sound/items/respawn1.wav" ); + + //cgs.media.noAmmoSound = trap_S_RegisterSound( "sound/weapons/noammo.wav" ); + + cgs.media.talkSound = trap_S_RegisterSound( "sound/interface/communicator.wav" ); + + //cgs.media.landSound = trap_S_RegisterSound( "sound/player/land1.wav"); + //RPG-X | GSIO01 | 20/05/2009: + cgs.media.landSound[LANDSOUND_NORMAL] = trap_S_RegisterSound("sound/player/land1.wav"); + cgs.media.landSound[LANDSOUND_GRASS] = trap_S_RegisterSound("sound/player/footsteps/grass_jump.wav"); + cgs.media.landSound[LANDSOUND_GRAVEL] = trap_S_RegisterSound("sound/player/footsteps/gravel_jump.wav"); + cgs.media.landSound[LANDSOUND_SNOW] = trap_S_RegisterSound("sound/player/footsteps/snow_jump.wav"); + cgs.media.landSound[LANDSOUND_WOOD] = trap_S_RegisterSound("sound/player/footsteps/wood_jump.wav"); + + cgs.media.splatSound = trap_S_RegisterSound( "sound/weapons/bodyfall.wav"); + + //cgs.media.hitSound = trap_S_RegisterSound( "sound/feedback/hit.wav" ); + //cgs.media.shieldHitSound = trap_S_RegisterSound( "sound/feedback/shieldHit.wav" ); + //cgs.media.shieldPierceSound = trap_S_RegisterSound( "sound/feedback/shieldPierce.wav" ); + + //TiM + /*cgs.media.rewardImpressiveSound = trap_S_RegisterSound( "sound/voice/computer/misc/impressive.wav" ); + cgs.media.rewardExcellentSound = trap_S_RegisterSound( "sound/voice/computer/misc/excellent.wav" ); + cgs.media.rewardDeniedSound = trap_S_RegisterSound( "sound/voice/computer/misc/denied.wav" ); + cgs.media.rewardFirstStrikeSound = trap_S_RegisterSound( "sound/voice/computer/misc/1ststrike.wav"); + cgs.media.rewardAceSound = trap_S_RegisterSound( "sound/voice/computer/misc/ace.wav"); + cgs.media.rewardExpertSound = trap_S_RegisterSound( "sound/voice/computer/misc/expert.wav"); + cgs.media.rewardMasterSound = trap_S_RegisterSound( "sound/voice/computer/misc/master.wav"); + cgs.media.rewardChampionSound = trap_S_RegisterSound( "sound/voice/computer/misc/champion.wav");*/ + + //TiM + /*cgs.media.takenLeadSound = trap_S_RegisterSound( "sound/voice/computer/misc/takenlead.wav"); + cgs.media.tiedLeadSound = trap_S_RegisterSound( "sound/voice/computer/misc/tiedlead.wav"); + cgs.media.lostLeadSound = trap_S_RegisterSound( "sound/voice/computer/misc/lostlead.wav");*/ + + cgs.media.watrInSound = trap_S_RegisterSound( "sound/player/watr_in.wav"); + cgs.media.watrOutSound = trap_S_RegisterSound( "sound/player/watr_out.wav"); + cgs.media.watrUnSound = trap_S_RegisterSound( "sound/player/watr_un.wav"); + + cgs.media.jumpPadSound = trap_S_RegisterSound ("sound/items/damage3.wav" ); + + //cgs.media.poweruprespawnSound = trap_S_RegisterSound ("sound/items/poweruprespawn.wav"); + cgs.media.disintegrateSound = trap_S_RegisterSound( "sound/weapons/prifle/disint.wav" ); + cgs.media.disintegrate2Sound = trap_S_RegisterSound( "sound/weapons/prifle/disint2.wav" ); + cgs.media.playerExplodeSound = trap_S_RegisterSound( "sound/weapons/explosions/fireball.wav" ); + + //TiM + /*cgs.media.holoInitSound = trap_S_RegisterSound("sound/voice/computer/misc/proginit.wav"); + cgs.media.holoDoorSound = trap_S_RegisterSound("sound/movers/doors/holoopen.wav"); + cgs.media.holoFadeSound = trap_S_RegisterSound("sound/movers/holodeckdecloak.wav");*/ + + cgs.media.phaserEmptySound = trap_S_RegisterSound("sound/weapons/phaser/phaserempty.wav"); + + //RPG-X: RedTechie - Load sound for shake cmd + cgs.media.ShakeSound = trap_S_RegisterSound("sound/shake.wav"); + + cgs.media.tedTextSound = trap_S_RegisterSound( "sound/interface/tedtext.wav" ); + + //RPG-X | Phenix | 13/02/2005 + for (i=0 ; i= GT_TEAM || cg_buildScript.integer ) { + cgs.media.teamRedShader = trap_R_RegisterShader( "sprites/team_red" ); + cgs.media.teamBlueShader = trap_R_RegisterShader( "sprites/team_blue" ); + cgs.media.redQuadShader = trap_R_RegisterShader("powerups/blueflag" ); + cgs.media.teamStatusBar = trap_R_RegisterShader( "gfx/2d/colorbar.tga" ); + }*/ + + //cgs.media.chatShader = trap_R_RegisterShader( "sprites/chat" ); + + //cgs.media.bloodExplosionShader = trap_R_RegisterShader( "bloodExplosion" ); + + //cgs.media.ringFlashModel = trap_R_RegisterModel("models/weaphits/ring02.md3"); + cgs.media.teleportEffectModel = trap_R_RegisterModel( "models/misc/telep.md3" ); + cgs.media.teleportEffectShader = trap_R_RegisterShader( "playerTeleport" ); + + //cgs.media.doorbox = trap_R_RegisterModel( "models/mapobjects/podium/hm_room.md3"); + + //RPG-X TiM : the bolton assets + cgs.media.phaserHolster = trap_R_RegisterModel( "models/boltOns/phaser_holster.md3"); + cgs.media.phaserHolsterInner = trap_R_RegisterModel( "models/boltOns/phaser_holster_inner.md3"); + + cgs.media.tricorderHolster = trap_R_RegisterModel( "models/boltOns/tricorder_holster.md3"); + cgs.media.tricorderHolsterInner = trap_R_RegisterModel( "models/boltOns/tricorder_holster_inner.md3"); + + //TiM : TR116 Eyescope + cgs.media.tr116EyeScope = trap_R_RegisterModel( "models/boltOns/tr116_scope.md3"); + //TiM : Flashlight + cgs.media.simsModule = trap_R_RegisterModel( "models/boltOns/sims_beacon.md3" ); + //EVA FPS Helmet + cgs.media.evaInterior = trap_R_RegisterModel( "models/boltOns/eva_interior.md3" ); + //Medical Scanner + cgs.media.medicalScanner = trap_R_RegisterModel( "models/weapons2/tricorder/tricorder_scanner.md3" ); + cgs.media.hazardHelmet = trap_R_RegisterModel( "models/boltOns/helmet.md3" ); + + //RPG-X START | GSIO01 | 09/05/2009 | START + switch(rpg_forceFieldSet.integer) { + case 2: + cgs.media.shieldActivateShaderBorg = trap_R_RegisterShader( "gfx/forcefielddarkgreen" ); + cgs.media.shieldActivateShaderYellow = trap_R_RegisterShader( "gfx/forcefielddarkyellow" ); + cgs.media.shieldActivateShaderRed = trap_R_RegisterShader( "gfx/forcefielddarkred" ); + cgs.media.shieldActivateShaderBlue = trap_R_RegisterShader( "gfx/forcefielddarkblue" ); + break; + case 1: + default: + cgs.media.shieldActivateShaderBorg = trap_R_RegisterShader( "gfx/ff_green" ); + cgs.media.shieldActivateShaderYellow = trap_R_RegisterShader( "gfx/ff_yellow" ); + cgs.media.shieldActivateShaderRed = trap_R_RegisterShader( "gfx/ff_red" ); + cgs.media.shieldActivateShaderBlue = trap_R_RegisterShader( "gfx/ff_blue" ); + break; + } + //RPG-X END + //cgs.media.shieldDamageShaderBlue = trap_R_RegisterShader( "gfx/misc/blue_dmgshield" ); + + + + //cgs.media.shieldActivateShaderRed = trap_R_RegisterShader( "gfx/misc/red_portashield" ); + //cgs.media.shieldDamageShaderRed = trap_R_RegisterShader( "gfx/misc/red_dmgshield" ); + + cgs.media.weaponPlaceholderShader = trap_R_RegisterShader("powerups/placeholder" ); + cgs.media.rezOutShader = trap_R_RegisterShader("powerups/rezout"); + cgs.media.electricBodyShader = trap_R_RegisterShader("gfx/misc/electric"); + + /*cgs.media.medalImpressive = trap_R_RegisterShaderNoMip( "medal_impressive" ); + cgs.media.medalExcellent = trap_R_RegisterShaderNoMip( "medal_excellent" ); + cgs.media.medalFirstStrike = trap_R_RegisterShaderNoMip( "medal_firststrike" ); + cgs.media.medalAce = trap_R_RegisterShaderNoMip( "medal_ace" ); + cgs.media.medalExpert = trap_R_RegisterShaderNoMip( "medal_expert" ); + cgs.media.medalMaster = trap_R_RegisterShaderNoMip( "medal_master" ); + cgs.media.medalChampion = trap_R_RegisterShaderNoMip( "medal_champion" );*/ + + //RPG-X: RedTechie - Scoreboard Endcaps + cgs.media.scoreboardtopleft = trap_R_RegisterShaderNoMip( "menu/common/rpgx_sb_topleft"); + cgs.media.scoreboardtopright = trap_R_RegisterShaderNoMip( "menu/common/rpgx_sb_topright"); + cgs.media.scoreboardbotleft = trap_R_RegisterShaderNoMip( "menu/common/rpgx_sb_bottomleft"); + cgs.media.scoreboardbotright = trap_R_RegisterShaderNoMip( "menu/common/rpgx_sb_bottomright"); + + //RPG-X: RedTechie - Healthbar Curves + cgs.media.healthendcap = trap_R_RegisterShaderNoMip("gfx/interface/rpgx_healthbar_endcap"); + cgs.media.healthbigcurve = trap_R_RegisterShaderNoMip("gfx/interface/rpgx_healthbar_leftcorner"); + //TiM: New Healthbar Graphics + //cgs.media.healthSineWave = trap_R_RegisterShaderNoMip( "menu/healthbar/sinewave" ); + + //RPG-X: RedTechie - Cloak Sprite + //cgs.media.cloakspriteShader = trap_R_RegisterShader("sprites/cloak"); + + cgs.media.scoreboardEndcap = trap_R_RegisterShaderNoMip( "menu/common/halfround_r_24"); + cgs.media.corner_12_24 = trap_R_RegisterShaderNoMip( "menu/common/corner_ll_24_12"); + cgs.media.corner_8_16_b = trap_R_RegisterShaderNoMip( "menu/common/corner_lr_8_16_b"); + + cgs.media.weaponcap1 = trap_R_RegisterShaderNoMip("gfx/interface/cap4"); + cgs.media.weaponcap2 = trap_R_RegisterShaderNoMip("gfx/interface/cap5"); + + cgs.media.weaponbox = trap_R_RegisterShaderNoMip("gfx/interface/weapon_box"); + cgs.media.weaponbox2 = trap_R_RegisterShaderNoMip("gfx/interface/weapon_box2"); + + memset( cg_items, 0, sizeof( cg_items ) ); + memset( cg_weapons, 0, sizeof( cg_weapons ) ); + + cg.loadLCARSStage = 5; // Loading bar stage 5 + //don't need a CG_LoadingString because there will be one in the LoadingItem() + + // only register the items that the server says we need + strcpy( items, CG_ConfigString( CS_ITEMS) ); + + for ( i = 1 ; i < bg_numItems ; i++ ) { + if ( items[ i ] == '1' || cg_buildScript.integer ) { + CG_LoadingItem( i ); + CG_RegisterItemVisuals( i ); + } + } + + // wall marks + cgs.media.holeMarkShader = trap_R_RegisterShader( "gfx/damage/hole_lg_mrk" ); + cgs.media.energyMarkShader = trap_R_RegisterShader( "gfx/damage/plasma_mrk" ); + cgs.media.shadowMarkShader = trap_R_RegisterShader( "markShadow" ); + cgs.media.wakeMarkShader = trap_R_RegisterShader( "wake" ); + + // Radar + //TiM: OPTIMIZATION: + //It's possible to use one set of textures, and use Alpha channels to vary + //the color thru the code... wouldn't that be better? + cgs.media.radarShader = trap_R_RegisterShader( "gfx/radar/radar" ); + /*cgs.media.rd_up = trap_R_RegisterShader( "gfx/radar/rd_up" ); + cgs.media.rd_down = trap_R_RegisterShader( "gfx/radar/rd_down" ); + cgs.media.rd_level = trap_R_RegisterShader( "gfx/radar/rd_level" ); + cgs.media.rd_red_up = trap_R_RegisterShader( "gfx/radar/rd_red_up" ); + cgs.media.rd_red_down = trap_R_RegisterShader( "gfx/radar/rd_red_down" ); + cgs.media.rd_red_level = trap_R_RegisterShader( "gfx/radar/rd_red_level" ); + cgs.media.rd_blue_up = trap_R_RegisterShader( "gfx/radar/rd_blue_up" ); + cgs.media.rd_blue_down = trap_R_RegisterShader( "gfx/radar/rd_blue_down" ); + cgs.media.rd_blue_level = trap_R_RegisterShader( "gfx/radar/rd_blue_level" ); + cgs.media.rd_white_up = trap_R_RegisterShader( "gfx/radar/rd_white_up" ); + cgs.media.rd_white_down = trap_R_RegisterShader( "gfx/radar/rd_white_down" ); + cgs.media.rd_white_level = trap_R_RegisterShader( "gfx/radar/rd_white_level" ); + cgs.media.rd_teal_up = trap_R_RegisterShader( "gfx/radar/rd_teal_up" ); + cgs.media.rd_teal_down = trap_R_RegisterShader( "gfx/radar/rd_teal_down" ); + cgs.media.rd_teal_level = trap_R_RegisterShader( "gfx/radar/rd_teal_level" ); + cgs.media.rd_black_up = trap_R_RegisterShader( "gfx/radar/rd_black_up" ); + cgs.media.rd_black_down = trap_R_RegisterShader( "gfx/radar/rd_black_down" ); + cgs.media.rd_black_level = trap_R_RegisterShader( "gfx/radar/rd_black_level" ); + cgs.media.rd_injured_up = trap_R_RegisterShader( "gfx/radar/injured_up" ); + cgs.media.rd_injured_down = trap_R_RegisterShader( "gfx/radar/injured_down" );*/ + cgs.media.rd_injured_level = trap_R_RegisterShader( "gfx/radar/injured_level" ); + + cgs.media.radarMain = trap_R_RegisterShaderNoMip( "gfx/radar/radar_icon" ); + + // register the inline models + cgs.numInlineModels = trap_CM_NumInlineModels(); + for ( i = 1 ; i < cgs.numInlineModels ; i++ ) { + char name[10]; + vec3_t mins, maxs; + int j; + + Com_sprintf( name, sizeof(name), "*%i", i ); + cgs.inlineDrawModel[i] = trap_R_RegisterModel( name ); + trap_R_ModelBounds( cgs.inlineDrawModel[i], mins, maxs ); + for ( j = 0 ; j < 3 ; j++ ) { + cgs.inlineModelMidpoints[i][j] = mins[j] + 0.5 * ( maxs[j] - mins[j] ); + } + } + + cg.loadLCARSStage = 6; // Loading bar stage 6 + CG_LoadingString( "Game Models" ); + + // register all the server specified models + for (i=1 ; i 0 ) { + cgs.locations = qtrue; + break; + } + } + + //TiM: Do the same for all tricorder string names + /*or ( i = 1; i < MAX_TRIC_STRINGS; i++ ) { + const char *strName; + + strName = CG_ConfigString( CS_TRIC_STRINGS+i ); + if ( !strName[0] ) { + break; + } + + //Com_Printf( S_COLOR_RED "USABLE MESSAGE IN CG: %s\n", strName ); + + //Com_sprintf( cgs.tricStrings[i], MAX_TOKEN_CHARS, "%s", strName ); + + //cgs.tricStrings[i] = (char *)strName; + //Com_Printf( S_COLOR_RED "%s\n", cgs.tricStrings[i] ); + Q_strncpyz( cgs.tricStrings[i], strName, MAX_TOKEN_CHARS ); + }*/ + + cg.loadLCARSStage = 7; // Loading bar stage 7 + CG_LoadingString( "Interface" ); + + // Registering interface graphics + for (i=0;i= MAX_CONFIGSTRINGS ) { + CG_Error( "CG_ConfigString: bad index: %i", index ); + } + return cgs.gameState.stringData + cgs.gameState.stringOffsets[ index ]; +} + +//================================================================== + +/* +====================== +CG_StartMusic + +====================== +*/ +void CG_StartMusic( void ) { + char *s; + char parm1[MAX_QPATH], parm2[MAX_QPATH]; + + // start the background music + s = (char *)CG_ConfigString( CS_MUSIC ); + Q_strncpyz( parm1, COM_Parse( &s ), sizeof( parm1 ) ); + Q_strncpyz( parm2, COM_Parse( &s ), sizeof( parm2 ) ); + + trap_S_StartBackgroundTrack( parm1, parm2 ); +} + +extern int altAmmoUsage[]; +void CG_InitModRules( void ) +{ + if ( cgs.pModDisintegration ) + {//don't use up ammo in disintegration mode + altAmmoUsage[WP_COMPRESSION_RIFLE] = 0; + } + if ( cgs.pModSpecialties ) + {//tripwires use more ammo + altAmmoUsage[WP_GRENADE_LAUNCHER] = 3; + } +} + +/* +================= +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 ) { + const char *s; + int i; + + // clear everything + memset( &cgs, 0, sizeof( cgs ) ); + memset( &cg, 0, sizeof( cg ) ); + memset( cg_entities, 0, sizeof(cg_entities) ); + memset( cg_weapons, 0, sizeof(cg_weapons) ); + memset( cg_items, 0, sizeof(cg_items) ); + + //Flush Sound Effects + memset( &cg_animsSndList, 0, sizeof( cg_animsSndList ) ); + + init_tonextint(qfalse); + + cgs.processedSnapshotNum = serverMessageNum; + cgs.serverCommandSequence = serverCommandSequence; + + CG_LoadIngameText(); + + CG_LoadFonts(); + + // Loading graphics + cgs.media.loading1 = trap_R_RegisterShaderNoMip( "menu/loading/smpiece1.tga" ); + cgs.media.loading2 = trap_R_RegisterShaderNoMip( "menu/loading/smpiece2.tga" ); + cgs.media.loading3 = trap_R_RegisterShaderNoMip( "menu/loading/smpiece3.tga" ); + cgs.media.loading4 = trap_R_RegisterShaderNoMip( "menu/loading/smpiece4.tga" ); + cgs.media.loading5 = trap_R_RegisterShaderNoMip( "menu/loading/smpiece5.tga" ); + cgs.media.loading6 = trap_R_RegisterShaderNoMip( "menu/loading/smpiece6.tga" ); + cgs.media.loading7 = trap_R_RegisterShaderNoMip( "menu/loading/smpiece7.tga" ); + cgs.media.loading8 = trap_R_RegisterShaderNoMip( "menu/loading/smpiece8.tga" ); + cgs.media.loading9 = trap_R_RegisterShaderNoMip( "menu/loading/smpiece9.tga" ); + cgs.media.loadingcircle = trap_R_RegisterShaderNoMip( "menu/loading/arrowpiece.tga" ); + cgs.media.loadingquarter= trap_R_RegisterShaderNoMip( "menu/loading/quarter.tga" ); + cgs.media.loadingcorner = trap_R_RegisterShaderNoMip( "menu/common/corner_lr_8_16.tga" ); + cgs.media.loadingtrim = trap_R_RegisterShaderNoMip( "menu/loading/trimupper.tga" ); + cgs.media.circle = trap_R_RegisterShaderNoMip( "menu/common/circle.tga" ); + cgs.media.circle2 = trap_R_RegisterShaderNoMip( "menu/objectives/circle_out.tga" ); + cgs.media.corner_12_18 = trap_R_RegisterShaderNoMip( "menu/common/corner_ll_12_18.tga" ); + cgs.media.halfroundr_22 = trap_R_RegisterShaderNoMip( "menu/common/halfroundr_22.tga" ); + + cgs.media.corner_ul_20_30= trap_R_RegisterShaderNoMip( "menu/common/corner_ul_20_30.tga" ); + cgs.media.corner_ll_8_30= trap_R_RegisterShaderNoMip( "menu/common/corner_ll_8_30.tga" ); + + cg.loadLCARSStage = 0; + cg.loadLCARScnt = 0; + // load a few needed things before we do any screen updates + cgs.media.charsetShader = trap_R_RegisterShaderNoMip( "gfx/2d/charsgrid_med" ); + cgs.media.whiteShader = trap_R_RegisterShader( "white" ); + cgs.media.white2Shader = trap_R_RegisterShader( "white2" ); + cgs.media.charsetPropTiny = trap_R_RegisterShaderNoMip("gfx/2d/chars_tiny"); + cgs.media.charsetProp = trap_R_RegisterShaderNoMip("gfx/2d/chars_medium"); + cgs.media.charsetPropBig = trap_R_RegisterShaderNoMip("gfx/2d/chars_big"); +// cgs.media.charsetPropGlow = trap_R_RegisterShaderNoMip( "menu/art/font1_prop_glo.tga" ); + cgs.media.charsetPropB = trap_R_RegisterShaderNoMip( "gfx/2d/chars_medium.tga" ); + + CG_RegisterCvars(); + + CG_InitConsoleCommands(); + + BG_LoadItemNames(); + + cg.weaponSelect = WP_NULL_HAND; + + cgs.redflag = cgs.blueflag = -1; // For compatibily, default to unset for + // old servers + + // get the rendering configuration from the client system + trap_GetGlconfig( &cgs.glconfig ); + cgs.screenXScale = cgs.glconfig.vidWidth / 640.0; + cgs.screenYScale = cgs.glconfig.vidHeight / 480.0; + + //TiM - handle wide screens + if ( cgs.glconfig.vidWidth * 480 > cgs.glconfig.vidHeight * 640 ) + { + cgs.widescreen.ratio = 640.0f*cgs.screenYScale * (1.0f/cgs.glconfig.vidWidth); + cgs.widescreen.bias = 0.5 * ( cgs.glconfig.vidWidth - ( cgs.glconfig.vidHeight * (640.0/480.0) ) ); + } + else + { + cgs.widescreen.ratio = 0; + cgs.widescreen.bias = 0; + } + + // get the gamestate from the client system + trap_GetGameState( &cgs.gameState ); + + // check version + s = CG_ConfigString( CS_GAME_VERSION ); + if ( strcmp( s, GAME_VERSION ) ) { + CG_Error( "Client/Server game mismatch: %s/%s", GAME_VERSION, s ); + } + + s = CG_ConfigString( CS_LEVEL_START_TIME ); + cgs.levelStartTime = atoi( s ); + + CG_ParseServerinfo(); + + // load the new map + CG_LoadingString( "collision map" ); + + trap_CM_LoadMap( cgs.mapname ); + + cg.loading = qtrue; // force players to load instead of defer + + CG_LoadObjectivesForMap(); + + CG_RegisterSounds(); + + CG_RegisterGraphics(); + + CG_RegisterClients(); // if low on memory, some clients will be deferred + + cg.loading = qfalse; // future players will be deferred + + CG_InitLocalEntities(); + + CG_InitMarkPolys(); + + // remove the last loading update + cg.infoScreenText[0] = 0; + + // Make sure we have update values (scores) + CG_SetConfigValues(); + + CG_StartMusic(); + + CG_LoadingString( "" ); + + //RPG-X | GSIO01 | 08/05/09: + //Make sure all weapons are registered to prevent + //missing models when someone get killed and weapons are dropped + for(i = 0; i < WP_NUM_WEAPONS; i++) { + CG_RegisterWeapon(i); + } + + // To get the interface timing started + cg.interfaceStartupTime = 0; + cg.interfaceStartupDone = 0; + + CG_InitModRules(); + + if ( !CG_LoadCrosshairs() ) + { + CG_Error( "Couldn't load crosshairs script" ); + } + + if ( !CG_LoadRanks() ) + { + CG_Error( "Couldn't load rankset script: %s", cgs.rankSet ); + } + + if ( !CG_LoadClasses() ) + { + CG_Error( "Couldn't load classset script: %s", cgs.classSet ); + } + + //where possible, load in the usable strings locally + if ( cgs.scannablePanels ) + CG_LoadUsablesStrings(); + + //TiM Finally, init class data received from Server + //TiM2 - Separated this out so class data has to be locally accessed now + //CG_ParseClassData(); + + #ifdef XTRA + CG_ShaderStateChanged(); + #endif + + if(grp_berp.integer) + CG_Printf(S_COLOR_YELLOW "GSIO01 and Ubergames greet Brave Explorers.\n"); +} + +/* +================= +CG_Shutdown + +Called before every level change or subsystem restart +================= +*/ +void CG_Shutdown( void ) { + // some mods may need to do cleanup work here, + // like closing files or archiving session data + //trap_Cvar_Set ("rpg_playIntro", "0"); +} + + +#define MAXINGAMETEXT 5000 +char ingameText[MAXINGAMETEXT]; + +/* +================= +CG_ParseIngameText +================= +*/ +void CG_ParseIngameText(void) +{ + char *token; + char *buffer; + int i; + int len; + + COM_BeginParseSession(); + + buffer = ingameText; + i = 1; // Zero is null string + while ( buffer ) + { + token = COM_ParseExt( &buffer, qtrue ); + + len = strlen(token); + if (len) + { + ingame_text[i] = (buffer - (len + 1)); // The +1 is to get rid of the " at the beginning of the sting. + *(buffer - 1) = '\0'; // Place an string end where is belongs. + + ++i; + } + + if (i> IGT_MAX) + { + Com_Printf( S_COLOR_RED "CG_ParseIngameText : too many values!\n"); + return; + } + } + + if (i != IGT_MAX) + { + Com_Printf( S_COLOR_RED "CG_ParseIngameText : not enough lines! Read %d of %d!\n",i,IGT_MAX); + for(;i MAXINGAMETEXT) + { + Com_Printf( S_COLOR_RED "CG_LoadIngameText : mp_ingametext.dat file bigger than %d!\n",MAXINGAMETEXT); + return; + } + + // initialise the data area + memset(ingameText, 0, sizeof(ingameText)); + + trap_FS_Read( ingameText, len, f ); + + trap_FS_FCloseFile( f ); + + + CG_ParseIngameText(); + +} + +/* +================= +CG_LoadObjectivesForMap +================= +*/ +void CG_LoadObjectivesForMap(void) +{ + int len, objnum = 0; + char *token; + char *buf; + fileHandle_t f; + char fileName[MAX_QPATH]; + char fullFileName[MAX_QPATH]; + char objtext[MAX_OBJ_TEXT_LENGTH]; + + COM_StripExtension( cgs.mapname, fileName ); + CG_LanguageFilename( fileName, "efo", fullFileName); + + len = trap_FS_FOpenFile( fullFileName, &f, FS_READ ); + + if ( len > MAX_OBJ_TEXT_LENGTH ) + { + Com_Printf( S_COLOR_RED "CG_LoadObjectivesForMap : %s file bigger than %d!\n", fileName, MAX_OBJ_TEXT_LENGTH ); + return; + } + + trap_FS_Read( objtext, len, f ); + + trap_FS_FCloseFile( f ); + + buf = objtext; + //Now parse out each objective + while ( 1 ) + { + token = COM_ParseExt( &buf, qtrue ); + if ( !token[0] ) { + break; + } + + // Normal objective text + if ( !Q_strncmp( token, "obj", 3 ) ) + { + objnum = atoi( &token[3] ); + + if ( objnum < 1 || objnum == MAX_OBJECTIVES ) { + Com_Printf( "Invalid objective number (%d), valid range is 1 to %d\n", objnum, MAX_OBJECTIVES ); + break; + } + + //Now read the objective text into the current objective + token = COM_ParseExt( &buf, qfalse ); + Q_strncpyz( cgs.objectives[objnum-1].text, token, sizeof(cgs.objectives[objnum-1].text) ); + } + + else if ( !Q_strncmp( token, "abridged_obj", 12 ) ) + { + objnum = atoi( &token[12] ); + + if ( objnum < 1 || objnum == MAX_OBJECTIVES ) + { + Com_Printf( "Invalid objective number (%d), valid range is 1 to %d\n", objnum, MAX_OBJECTIVES ); + break; + } + + //Now read the objective text into the current objective + token = COM_ParseExt( &buf, qfalse ); + Q_strncpyz( cgs.objectives[objnum-1].abridgedText, token, sizeof(cgs.objectives[objnum-1].abridgedText) ); + } + } +} + +qboolean CG_LoadClasses( void ) +{ + fileHandle_t f; + int file_len; + char buffer[32000]; + char *token, *textPtr; + char filePath[MAX_QPATH]; + int numClasses=0; + int i; + + Com_sprintf( filePath, sizeof( filePath ), "ext_data/classes/%s.classes", cgs.classSet ); + + memset( &cgs.classData, 0, sizeof( cg_classData_t ) ); + + file_len = trap_FS_FOpenFile( filePath, &f, FS_READ ); + + if ( !file_len ) + { + CG_Printf( S_COLOR_RED "Couldn't find class file: %s\n", filePath ); + return qfalse; + } + + if ( file_len > sizeof( buffer ) ) + { + CG_Printf( S_COLOR_RED "File %s was way too big to be loaded.\n", filePath ); + return qfalse; + } + + trap_FS_Read( buffer, file_len, f ); + trap_FS_FCloseFile( f ); + + buffer[file_len] = '\0'; + + COM_BeginParseSession(); + + textPtr = buffer; + + token = COM_Parse( &textPtr ); + + if ( !token[0] ) { + CG_Printf( S_COLOR_RED "ERROR: No data was found when going to parse the file!\n" ); + return qfalse; + } + + if ( Q_stricmpn( token, "{", 1 ) ) { + CG_Printf( S_COLOR_RED "ERROR: File did not start with a '{' symbol!\n" ); + return qfalse; + } + + while( 1 ) + { + if ( numClasses >= MAX_CLASSES ) + break; + + + if ( !Q_strncmp( token, "{", 1 ) ) + { + while ( 1 ) + { + token = COM_Parse( &textPtr ); + + if (!token[0]) { + break; + } + + if ( !Q_stricmpn( token, "formalName", 10 ) ) + { + if ( COM_ParseString( &textPtr, &token ) ) + { + CG_Printf( S_COLOR_RED "ERROR: Invalid class formal name in class index: %i.\n", numClasses ); + continue; + } + + Q_strncpyz( cgs.classData[numClasses].formalName, token, sizeof( cgs.classData[numClasses].formalName ) ); + continue; + } + + if ( !Q_stricmpn( token, "radarColor", 5 ) ) + { + vec3_t temp; + + if ( COM_ParseVec3( &textPtr, temp ) ) + { + CG_Printf( S_COLOR_RED "ERROR: Invalid color values in class index: %i.\n", numClasses ); + continue; + } + + for ( i = 0; i < 3; i++ ) + { + cgs.classData[numClasses].radarColor[i] = (int)Com_Clamp( 0, 255, (int)temp[i] ); + //G_Printf( S_COLOR_RED "g_classData[numClasses].color[%i] = %i\n", i, g_classData[numClasses].color[i] ); + } + + continue; + } + + if ( !Q_stricmpn( token, "iconColor", 9 ) ) + { + if ( COM_ParseString( &textPtr, &token ) ) + { + CG_Printf( S_COLOR_RED "ERROR: Invalid class icon color in class index: %i.\n", numClasses ); + continue; + } + + //Eh... there are enum values for these, + //but they're currently out of scope ;P + if ( !Q_stricmp( token, "red" ) ) + { + cgs.classData[numClasses].iconColor = 1; //CLR_RED + } + else if ( !Q_stricmp( token, "gold" ) ) + { + cgs.classData[numClasses].iconColor = 2; //CLR_GOLD + } + else if ( !Q_stricmp( token, "teal" ) ) + { + cgs.classData[numClasses].iconColor = 3; //CLR_TEAL + } + else if ( !Q_stricmp( token, "green" ) ) + { + cgs.classData[numClasses].iconColor = 4; //CLR_GREEN + } + else + { + cgs.classData[numClasses].iconColor = 0; //0 + } + + continue; + } + + if ( !Q_stricmpn( token, "medical", 7 ) ) + { + if ( COM_ParseInt( &textPtr, (int *)&cgs.classData[numClasses].isMedic ) ) + { + CG_Printf( S_COLOR_RED "ERROR: Class medic check for class %i was invalid.\n", numClasses ); + continue; + } + + continue; + } + + if( !Q_stricmpn( token, "isBorg", 6 ) ) + { + if( COM_ParseInt( &textPtr, (int *)&cgs.classData[numClasses].isBorg ) ) + { + CG_Printf( S_COLOR_RED "ERROR: Class borg check for class %i was invalid.\n", numClasses ); + continue; + } + continue; + } + + if ( !Q_stricmpn( token, "hasRanks", 8 ) ) + { + if ( COM_ParseInt( &textPtr, (int *)&cgs.classData[numClasses].showRanks ) ) + { + CG_Printf( S_COLOR_RED "ERROR: Class Ranks check for class %i was invalid.\n", numClasses ); + continue; + } + + continue; + } + + //I'm a n00b lol. I made a class called 'medical' and a parameter called 'medical'. + //I have to double check both parms or else it confuses the parser + //better check all of them. I'm still getting errors + if ( !Q_stricmpn( token, "consoleName", 10 ) + || !Q_stricmpn( token, "modelSkin", 9 ) + || !Q_stricmpn( token, "message", 7 ) + || !Q_stricmpn( token, "admin", 5 ) + || !Q_stricmpn( token, "marine", 6 ) + || !Q_stricmpn( token, "noShow", 6 ) + ) + { + SkipRestOfLine(&textPtr); + continue; + } + + //this one is a pain since it can potentially have multiple lines + if ( !Q_stricmpn( token, "weapons", 7 ) ) + { + SkipBracedSection( &textPtr ); + continue; + } + + if ( !Q_stricmpn( token, "}", 1 ) ) + { + numClasses++; + break; + } + } + } + + token = COM_Parse( &textPtr ); + + if (!token[0]) + { + break; + } + } + + if ( numClasses > 0 ) + return qtrue; + else + { + CG_Printf( S_COLOR_RED "ERROR: No classes were found in the class file!\n" ); + return qfalse; + } +} + +qboolean CG_LoadUsablesStrings( void ) +{ + char fileRoute[MAX_QPATH]; + char mapRoute[MAX_QPATH]; + char buffer[20000]; + int file_len; + char *textPtr, *token; + fileHandle_t f; + int i; + int strLen; + + //setup the file route + Com_sprintf( mapRoute, sizeof( mapRoute ), "%s", cgs.mapname ); + + strLen = strlen( mapRoute ); + + //*sigh* remove the bsp bit + if ( strLen > 4 && !Q_stricmp( mapRoute + strLen-4, ".bsp" ) ) + mapRoute[strLen-4] = '\0'; + + //check for language + CG_LanguageFilename( mapRoute, "usables", fileRoute ); + + file_len = trap_FS_FOpenFile( fileRoute, &f, FS_READ ); + + //It's assumed most maps won't have this feature, so just exit 'gracefully' + if ( file_len<=1 ) + { + //CG_Printf( S_COLOR_YELLOW "WARNING: No file named %s was found. If the server \n", fileRoute ); + trap_FS_FCloseFile( f ); + return qfalse; + } + + //fill the buffer with the file data + memset( &buffer, 0, sizeof( buffer ) ); + trap_FS_Read( buffer, file_len, f ); + buffer[file_len] = '0'; + + trap_FS_FCloseFile( f ); + + if ( !buffer[0] ) + { + CG_Printf( S_COLOR_RED "ERROR: Attempted to load %s, but no data was inside!\n", fileRoute ); + return qfalse; + } + + COM_BeginParseSession(); + textPtr = buffer; + + i = 0; //used for the main arrays indices + + while( 1 ) + { + token = COM_Parse( &textPtr ); + if ( !token[0] ) + break; + + if ( !Q_strncmp( token, "UsableDescriptions", 18 ) ) + { + token = COM_Parse( &textPtr ); + if ( Q_strncmp( token, "{", 1 ) != 0 ) + { + CG_Printf( S_COLOR_RED "ERROR: UsableDescriptions had no opening brace ( { )!\n", fileRoute ); + continue; + } + + token = COM_Parse( &textPtr ); + + //expected format is 'id' 'string' + while ( Q_strncmp( token, "}", 1 ) ) + { + if ( !token[0] ) + break; + + if ( !Q_strncmp( token, "UsableEntities", 14 ) ) + { + SkipBracedSection( &textPtr ); + continue; + } + else + { + //parse past the ID num + token = COM_ParseExt( &textPtr, qfalse ); + + //copy the line of text + if( token[0] ) + { + Q_strncpyz( cgs.scannableStrings[i], token, sizeof( cgs.scannableStrings[i] ) ); + i++; + } + + token = COM_Parse( &textPtr ); + } + } + } + } + + return qtrue; +} diff --git a/cgame/cg_main.c.orig b/cgame/cg_main.c.orig new file mode 100644 index 0000000..d37f2b5 --- /dev/null +++ b/cgame/cg_main.c.orig @@ -0,0 +1,1792 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_main.c -- initialization and primary entry point for cgame + +//TiM : 22/12/2005 - Commented out any assets that we no longer use. +//TiM : 24/12/2005 - Added a ranks parsing function. + +#include "cg_local.h" +#include "cg_text.h" + +void CG_Init( int serverMessageNum, int serverCommandSequence ); +void CG_Shutdown( void ); +void CG_LoadIngameText(void); +void CG_LoadObjectivesForMap(void); +void BG_LoadItemNames(void); + +//TiM - Placed the func @ the bottom of the page for easier access +//extern static qboolean CG_LoadRanks( void ); + +extern void FX_InitSinTable(void); + +//extern lensReflec_s lensReflec[10]; + +/* +================ +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 ) { + switch ( command ) { + case CG_INIT: + CG_Init( arg0, arg1 ); + 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(); + default: + CG_Error( "vmMain: unknown command %i", command ); + break; + } + return -1; +} + + +cg_t cg; +cgs_t cgs; +centity_t cg_entities[MAX_GENTITIES]; +weaponInfo_t cg_weapons[MAX_WEAPONS]; +itemInfo_t cg_items[MAX_ITEMS]; + +vmCvar_t cg_centertime; +vmCvar_t cg_runpitch; +vmCvar_t cg_runroll; +vmCvar_t cg_bobup; +vmCvar_t cg_bobpitch; +vmCvar_t cg_bobroll; +vmCvar_t cg_swingSpeed; +vmCvar_t cg_shadows; +vmCvar_t cg_gibs; +vmCvar_t cg_drawTimer; +vmCvar_t cg_drawFPS; +vmCvar_t cg_drawSnapshot; +vmCvar_t cg_draw3dIcons; +vmCvar_t cg_drawIcons; +vmCvar_t cg_drawAmmoWarning; +vmCvar_t cg_drawCrosshair; +vmCvar_t cg_drawCrosshairNames; +vmCvar_t cg_drawRewards; +vmCvar_t cg_crosshairSize; +vmCvar_t cg_crosshairX; +vmCvar_t cg_crosshairY; +vmCvar_t cg_crosshairHealth; +vmCvar_t cg_draw2D; +vmCvar_t cg_drawStatus; +vmCvar_t cg_animSpeed; +vmCvar_t cg_debugAnim; +vmCvar_t cg_debugPosition; +vmCvar_t cg_debugEvents; +vmCvar_t cg_errorDecay; +vmCvar_t cg_nopredict; +vmCvar_t cg_noPlayerAnims; +vmCvar_t cg_showmiss; +vmCvar_t cg_footsteps; +vmCvar_t cg_addMarks; +vmCvar_t cg_viewsize; +vmCvar_t cg_drawGun; +vmCvar_t cg_gun_frame; +vmCvar_t cg_gun_x; +vmCvar_t cg_gun_y; +vmCvar_t cg_gun_z; +vmCvar_t cg_autoswitch; +vmCvar_t cg_ignore; +vmCvar_t cg_simpleItems; +vmCvar_t cg_fov; +vmCvar_t cg_zoomFov; +vmCvar_t cg_thirdPerson; +vmCvar_t cg_thirdPersonRange; +vmCvar_t cg_thirdPersonAngle; +//RPG-X: TiM - Cool JKA CVARs +vmCvar_t cg_thirdPersonVertOffset; +vmCvar_t cg_thirdPersonHorzOffset; +vmCvar_t cg_thirdPersonAlpha; +vmCvar_t cg_thirdPersonCameraDamp; +vmCvar_t cg_thirdPersonTargetDamp; +vmCvar_t cg_thirdPersonPitchOffset; +vmCvar_t cg_stereoSeparation; +vmCvar_t cg_lagometer; +vmCvar_t cg_drawAttacker; +vmCvar_t cg_synchronousClients; +vmCvar_t cg_teamChatTime; +vmCvar_t cg_teamChatHeight; +vmCvar_t cg_stats; +vmCvar_t cg_reportDamage; +vmCvar_t cg_buildScript; +vmCvar_t cg_forceModel; +vmCvar_t cg_paused; +vmCvar_t cg_blood; +vmCvar_t cg_predictItems; +vmCvar_t cg_deferPlayers; +vmCvar_t cg_drawTeamOverlay; +vmCvar_t cg_teamOverlayUserinfo; +vmCvar_t ui_playerclass; +vmCvar_t ui_playerrank; +vmCvar_t cg_disablekillmsgs; +vmCvar_t cg_drawradar; +vmCvar_t rpg_ctribgrenade; //RPG-X: - RedTechie i think J2J added this to control invisible tripmines on clients end - TOFIX:THIS ALSO COULD BE HACKED! +vmCvar_t cg_dynamicCrosshair; //RPG-X | Phenix | 09/06/2005 +vmCvar_t doomHead; //RPG-X | Phenix | 09/06/2005 +vmCvar_t cg_dynamiclensflares; +vmCvar_t cg_noTalkingHeads; +vmCvar_t cg_noDynamicRanks; + +vmCvar_t noAdminChat; //TiM + +//RPG-X: TiM - Player Model Parameters +vmCvar_t pms_age; +vmCvar_t pms_height; +vmCvar_t pms_weight; +vmCvar_t pms_race; + +//RPG-X: TiM - Emote system model offset +vmCvar_t emote_Offset; + +vmCvar_t cg_thirdPersonZoomRate; +vmCvar_t cg_noFrowningHeads; +vmCvar_t cg_noBlinkingHeads; + +//RPG-X | Phenix | 05/02/2006 +//Ban System (and it's backup cvars) +vmCvar_t cg_playerID; +vmCvar_t s_mhz; //Part A + 562 +vmCvar_t cg_fow; //Part B + 333 +vmCvar_t cl_avgPacket; //Part C + 99 +vmCvar_t cg_rewardsSize;//Part D + 120 + +typedef struct { + vmCvar_t *vmCvar; + char *cvarName; + char *defaultString; + int cvarFlags; +} cvarTable_t; + +static cvarTable_t cvarTable[] = { + { &cg_ignore, "cg_ignore", "0", 0 }, // used for debugging + { &s_mhz, "s_mhz", "0", CVAR_ARCHIVE | CVAR_ROM | CVAR_NORESTART }, //RPG-X | Phenix | 05/02/2006 + { &cg_autoswitch, "cg_autoswitch", "1", CVAR_ARCHIVE }, + { &cg_drawGun, "cg_drawGun", "1", CVAR_ARCHIVE }, + { &cg_zoomFov, "cg_zoomfov", "22.5", CVAR_ARCHIVE }, + { &cg_fov, "cg_fov", "80", CVAR_ARCHIVE }, + { &cg_fow, "cg_fow", "0", CVAR_ARCHIVE | CVAR_ROM | CVAR_NORESTART }, //RPG-X | Phenix | 05/02/2006 + { &cg_viewsize, "cg_viewsize", "100", CVAR_ARCHIVE }, + { &cg_stereoSeparation, "cg_stereoSeparation", "0.4", CVAR_ARCHIVE }, + { &s_mhz, "s_mhz", "0", CVAR_ARCHIVE | CVAR_ROM | CVAR_NORESTART }, //RPG-X | Phenix | 05/02/2006 + { &cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE }, + { &cg_gibs, "cg_gibs", "0", CVAR_ARCHIVE }, //no gibs in trek + { &cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE }, + { &cg_drawStatus, "cg_drawStatus", "1", CVAR_ARCHIVE }, + { &cg_drawTimer, "cg_drawTimer", "0", CVAR_ARCHIVE }, + { &cg_drawFPS, "cg_drawFPS", "0", CVAR_ARCHIVE }, + { &cg_drawSnapshot, "cg_drawSnapshot", "0", CVAR_ARCHIVE }, + { &cg_draw3dIcons, "cg_draw3dIcons", "1", CVAR_ARCHIVE }, + { &cg_drawIcons, "cg_drawIcons", "1", CVAR_ARCHIVE }, + { &cg_drawAmmoWarning, "cg_drawAmmoWarning", "1", CVAR_ARCHIVE }, + { &cg_drawAttacker, "cg_drawAttacker", "0", CVAR_ARCHIVE }, //RPG-X TiM + { &cg_drawCrosshair, "cg_drawCrosshair", "1", CVAR_ARCHIVE }, + { &cg_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE }, + { &cg_drawRewards, "cg_drawRewards", "1", CVAR_ARCHIVE }, + { &cg_rewardsSize, "cg_rewardsSize", "0", CVAR_ARCHIVE | CVAR_ROM | CVAR_NORESTART }, //RPG-X | Phenix | 05/02/2006 + { &cg_crosshairSize, "cg_crosshairSize", "24", CVAR_ARCHIVE }, + { &cg_crosshairHealth, "cg_crosshairHealth", "1", CVAR_ARCHIVE }, + { &cg_crosshairX, "cg_crosshairX", "0", CVAR_ARCHIVE }, + { &cg_crosshairY, "cg_crosshairY", "0", CVAR_ARCHIVE }, + { &cg_simpleItems, "cg_simpleItems", "0", CVAR_ARCHIVE }, + { &cg_addMarks, "cg_marks", "1", 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", "3", CVAR_CHEAT }, + { &cg_runpitch, "cg_runpitch", "0.002", CVAR_ARCHIVE}, + { &cg_runroll, "cg_runroll", "0.005", CVAR_ARCHIVE }, + { &cg_bobup , "cg_bobup", "0.005", CVAR_ARCHIVE }, + { &cg_bobpitch, "cg_bobpitch", "0.002", CVAR_ARCHIVE }, + { &cg_bobroll, "cg_bobroll", "0.002", CVAR_ARCHIVE }, + { &cg_swingSpeed, "cg_swingSpeed", "0.3", CVAR_CHEAT }, + { &cg_animSpeed, "cg_animspeed", "1", CVAR_CHEAT }, + { &cg_debugAnim, "cg_debuganim", "0", CVAR_CHEAT }, + { &cg_debugPosition, "cg_debugposition", "0", CVAR_CHEAT }, + { &cg_debugEvents, "cg_debugevents", "0", CVAR_CHEAT }, + { &cg_errorDecay, "cg_errordecay", "100", 0 }, + { &cg_nopredict, "cg_nopredict", "0", 0 }, + { &cg_noPlayerAnims, "cg_noplayeranims", "0", CVAR_CHEAT }, + { &cg_showmiss, "cg_showmiss", "0", 0 }, + { &cg_footsteps, "cg_footsteps", "1", CVAR_CHEAT }, + { &cg_thirdPersonRange, "cg_thirdPersonRange", "80", CVAR_ARCHIVE }, + { &cg_thirdPersonAngle, "cg_thirdPersonAngle", "0", CVAR_ARCHIVE }, + { &cg_thirdPerson, "cg_thirdPerson", "0", CVAR_ARCHIVE }, + { &cg_thirdPersonVertOffset, "cg_thirdPersonVertOffset", "16", CVAR_ARCHIVE }, //RPG-X: TiM + { &cg_thirdPersonHorzOffset, "cg_thirdPersonHorzOffset", "0", CVAR_ARCHIVE }, //RPG-X: TiM + { &cg_thirdPersonAlpha, "cg_thirdPersonAlpha", "1.0", CVAR_ARCHIVE },//RPG-X: TiM + { &cg_thirdPersonCameraDamp, "cg_thirdPersonCameraDamp", "0.3", CVAR_ARCHIVE },//RPG-X: TiM + { &cg_thirdPersonTargetDamp, "cg_thirdPersonTargetDamp", "0.5", CVAR_ARCHIVE },//RPG-X: TiM + { &cg_thirdPersonPitchOffset, "cg_thirdPersonPitchOffset", "0.0", CVAR_ARCHIVE},//RPG-X: TiM + { &cg_teamChatTime, "cg_teamChatTime", "3000", CVAR_ARCHIVE }, + { &cg_teamChatHeight, "cg_teamChatHeight", "0", CVAR_ARCHIVE }, + { &cg_forceModel, "cg_forceModel", "0", CVAR_ARCHIVE }, + { &cg_predictItems, "cg_predictItems", "1", CVAR_ARCHIVE }, + { &cg_deferPlayers, "cg_deferPlayers", "1", CVAR_ARCHIVE }, + { &cg_drawTeamOverlay, "cg_drawTeamOverlay", "0", CVAR_ARCHIVE }, + { &cg_teamOverlayUserinfo, "teamoverlay", "0", CVAR_ROM | CVAR_USERINFO }, + { &cg_stats, "cg_stats", "0", 0 }, + { &cg_reportDamage, "cg_reportDamage", "0", 0}, + { &rpg_ctribgrenade, "rpg_ctribgrenade", "0", CVAR_ARCHIVE}, //RPG-X: - RedTechie i think J2J added this to control invisible tripmines on clients end - TOFIX:THIS ALSO COULD BE HACKED! + { &pms_age, "age", "Unknown", CVAR_ARCHIVE | CVAR_USERINFO }, + { &pms_height, "height", "1.0", CVAR_ARCHIVE | CVAR_USERINFO }, + { &pms_weight, "weight", "1.0", CVAR_ARCHIVE | CVAR_USERINFO }, + { &pms_race, "race", "Unknown", CVAR_ARCHIVE | CVAR_USERINFO }, + { &emote_Offset, "modelOffset", "0.0", CVAR_ARCHIVE | CVAR_USERINFO }, + + + // the following variables are created in other parts of the system, + // but we also reference them here + + { &cg_buildScript, "com_buildScript", "0", 0 }, // force loading of all possible data amd error on failures + { &cg_paused, "cl_paused", "0", CVAR_ROM }, + { &cg_blood, "com_blood", "0", CVAR_ARCHIVE }, //no blood in trek + { &cl_avgPacket, "cl_avgPacket", "0", CVAR_ARCHIVE | CVAR_ROM | CVAR_NORESTART }, //RPG-X | Phenix | 05/02/2006 + { &cg_synchronousClients, "g_synchronousClients", "0", 0 }, // communicated by systeminfo + { &ui_playerclass, "ui_playerclass", "noclass", CVAR_ARCHIVE | CVAR_ROM }, + //{ &ui_playerclass, "ui_playerclass", "0", 0 }, // player class + { &ui_playerrank, "ui_playerrank", "crewman", CVAR_ARCHIVE | CVAR_ROM }, + + { &cg_disablekillmsgs, "cg_disablekillmsgs", "0", CVAR_ARCHIVE }, + { &cg_drawradar, "cg_drawradar", "1", CVAR_ARCHIVE }, + + { &cg_dynamicCrosshair, "cg_dynamicCrosshair", "1", CVAR_ARCHIVE }, //RPG-X | Phenix | 09/06/2005 + { &doomHead, "doomHead", "0", CVAR_ARCHIVE }, //RPG-X | Phenix | 09/06/2005 + { &cg_dynamiclensflares, "cg_dynamicLensFlares", "1", CVAR_ARCHIVE }, //RPG-X | TiM | 29/6/2005 + + { &noAdminChat, "noAdminChat", "0", CVAR_ARCHIVE | CVAR_USERINFO }, + + //RPG-X Memory optimization CVARs + { &cg_noTalkingHeads, "cg_noTalkingHeads", "0", CVAR_ARCHIVE }, + { &cg_noDynamicRanks, "cg_noDynamicRanks", "0", CVAR_ARCHIVE }, + { &cg_noFrowningHeads, "cg_noFrowningHeads", "1", CVAR_ARCHIVE }, //On by default since this isn't REALLY needed... :P + { &cg_noBlinkingHeads, "cg_noBlinkingHeads", "0", CVAR_ARCHIVE }, + + { &cg_thirdPersonZoomRate, "cg_thirdPersonZoomRate", "25", CVAR_ARCHIVE }, + + { &cg_playerID, "cg_playerID", "0", CVAR_ARCHIVE | CVAR_ROM | CVAR_USERINFO | CVAR_NORESTART } //RPG-X | Phenix | 05/02/2006 +}; + +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 ); + } + + // see if we are also running the server on this machine + trap_Cvar_VariableStringBuffer( "sv_running", var, sizeof( var ) ); + cgs.localServer = atoi( var ); +} + + +/* +================= +CG_UpdateCvars +================= +*/ +void CG_UpdateCvars( void ) { + int i; + cvarTable_t *cv; + + for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) + { + trap_Cvar_Update( cv->vmCvar ); + } + + // check for modications here + + // If team overlay is on, ask for updates from the server. If its off, + // let the server know so we don't receive it + if ( drawTeamOverlayModificationCount != cg_drawTeamOverlay.modificationCount ) { + drawTeamOverlayModificationCount = cg_drawTeamOverlay.modificationCount; + + if ( cg_drawTeamOverlay.integer > 0 ) { + trap_Cvar_Set( "teamoverlay", "1" ); + } else { + trap_Cvar_Set( "teamoverlay", "0" ); + } + } +} + + +int CG_CrosshairPlayer( void ) { + if ( cg.time > ( cg.crosshairClientTime + 1000 ) ) { + return -1; + } + return cg.crosshairClientNum; +} + + +int CG_LastAttacker( void ) { + if ( !cg.attackerTime ) { + return -1; + } + return cg.snap->ps.persistant[PERS_ATTACKER]; +} + + +void QDECL CG_Printf( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + trap_Print( text ); +} + +void QDECL CG_Error( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + trap_Error( text ); +} + +#ifndef CGAME_HARD_LINKED +// this is only here so the functions in q_shared.c and bg_*.c can link (FIXME) + +void QDECL Com_Error( int level, const char *error, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, error); + vsprintf (text, error, argptr); + va_end (argptr); + + CG_Error( "%s", text); +} + +void QDECL Com_Printf( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + CG_Printf ("%s", text); +} + +#endif + + + +/* +================ +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) { + CG_Error( "PrecacheItem: %s has bad precache string", + item->classname); + return; + } + memcpy (data, start, len); + data[len] = 0; + if ( *s ) { + s++; + } + + if ( !strcmp(data+len-3, "wav" )) { + trap_S_RegisterSound( data ); + } + } +} + + +/* +================= +CG_RegisterSounds + +called during a precache command +================= +*/ +static void CG_RegisterSounds( void ) +{ + int i; + char items[MAX_ITEMS+1]; + char name[MAX_QPATH]; + const char *soundName; + + cg.loadLCARSStage = 1; // Loading bar stage 1 + CG_LoadingString( "sounds" ); + + //TiM + /*if ( cgs.timelimit || cg_buildScript.integer ) { // should we always load this? + cgs.media.oneMinuteSound = trap_S_RegisterSound( "sound/voice/computer/misc/1_minute.wav" ); + cgs.media.fiveMinuteSound = trap_S_RegisterSound( "sound/voice/computer/misc/5_minute.wav" ); + cgs.media.suddenDeathSound = trap_S_RegisterSound( "sound/voice/computer/misc/sudden_death.wav" ); + }*/ + + //TiM + /*if ( cgs.fraglimit || cg_buildScript.integer ) { + cgs.media.oneFragSound = trap_S_RegisterSound( "sound/voice/computer/misc/1_frag.wav" ); + cgs.media.twoFragSound = trap_S_RegisterSound( "sound/voice/computer/misc/2_frags.wav" ); + cgs.media.threeFragSound = trap_S_RegisterSound( "sound/voice/computer/misc/3_frags.wav" ); + }*/ + +// if ( cgs.gametype == GT_TOURNAMENT || cg_buildScript.integer ) { +// We always need this since a warmup can be enabled in any game mode +//TiM /*cgs.media.count3Sound = trap_S_RegisterSound( "sound/voice/computer/misc/three.wav" ); + cgs.media.count2Sound = trap_S_RegisterSound( "sound/voice/computer/misc/two.wav" ); + cgs.media.count1Sound = trap_S_RegisterSound( "sound/voice/computer/misc/one.wav" ); + cgs.media.countFightSound = trap_S_RegisterSound( "sound/voice/computer/misc/fight.wav" ); + cgs.media.countPrepareSound = trap_S_RegisterSound( "sound/voice/computer/misc/prepare.wav" ); +// } + //TiM +/* if ( cgs.gametype >= GT_TEAM || cg_buildScript.integer ) { + cgs.media.redLeadsSound = trap_S_RegisterSound( "sound/voice/computer/misc/redleads.wav" ); + cgs.media.blueLeadsSound = trap_S_RegisterSound( "sound/voice/computer/misc/blueleads.wav" ); + cgs.media.teamsTiedSound = trap_S_RegisterSound( "sound/voice/computer/misc/teamstied.wav" ); + cgs.media.hitTeamSound = trap_S_RegisterSound( "sound/feedback/hit_teammate.wav" ); + }*/ + + //TiM + /*if (cgs.gametype == GT_CTF || cg_buildScript.integer) + { + cgs.media.ctfStealSound = trap_S_RegisterSound("sound/voice/computer/misc/flagtk_blu.wav"); + cgs.media.ctfReturnSound = trap_S_RegisterSound("sound/voice/computer/misc/flagret_blu.wav"); + cgs.media.ctfScoreSound = trap_S_RegisterSound("sound/voice/computer/misc/flagcap_blu.wav"); + cgs.media.ctfYouStealVoiceSound = trap_S_RegisterSound("sound/voice/computer/misc/stolen.wav"); + cgs.media.ctfYouDroppedVoiceSound = trap_S_RegisterSound("sound/voice/computer/misc/dropped_e.wav"); + cgs.media.ctfYouReturnVoiceSound = trap_S_RegisterSound("sound/voice/computer/misc/returned.wav"); + cgs.media.ctfYouScoreVoiceSound = trap_S_RegisterSound("sound/voice/computer/misc/scored.wav"); + cgs.media.ctfTheyStealVoiceSound = trap_S_RegisterSound("sound/voice/computer/misc/stolen_e.wav"); + cgs.media.ctfTheyDroppedVoiceSound = trap_S_RegisterSound("sound/voice/computer/misc/dropped.wav"); // Note the flip, because YOU dropped THEIR flag + cgs.media.ctfTheyReturnVoiceSound = trap_S_RegisterSound("sound/voice/computer/misc/returned_e.wav"); + cgs.media.ctfTheyScoreVoiceSound = trap_S_RegisterSound("sound/voice/computer/misc/scored_e.wav"); + }*/ + + cgs.media.interfaceSnd1 = trap_S_RegisterSound( "sound/interface/button4.wav" ); + + cgs.media.selectSound = trap_S_RegisterSound( "sound/weapons/change.wav" ); + cgs.media.wearOffSound = trap_S_RegisterSound( "sound/items/wearoff.wav" ); + cgs.media.useNothingSound = trap_S_RegisterSound( "sound/items/use_nothing.wav" ); + + //TiM//cgs.media.holoOpenSound = trap_S_RegisterSound( "sound/movers/doors/holoopen.wav" ); + cgs.media.teleInSound = trap_S_RegisterSound( "sound/world/transin.wav" ); + cgs.media.teleOutSound = trap_S_RegisterSound( "sound/world/transout.wav" ); + cgs.media.transportSound = trap_S_RegisterSound( "sound/world/transporter.wav" ); + cgs.media.respawnSound = trap_S_RegisterSound( "sound/items/respawn1.wav" ); + + cgs.media.noAmmoSound = trap_S_RegisterSound( "sound/weapons/noammo.wav" ); + + cgs.media.talkSound = trap_S_RegisterSound( "sound/interface/communicator.wav" ); + cgs.media.landSound = trap_S_RegisterSound( "sound/player/land1.wav"); + cgs.media.splatSound = trap_S_RegisterSound( "sound/weapons/bodyfall.wav"); + + cgs.media.hitSound = trap_S_RegisterSound( "sound/feedback/hit.wav" ); + cgs.media.shieldHitSound = trap_S_RegisterSound( "sound/feedback/shieldHit.wav" ); + cgs.media.shieldPierceSound = trap_S_RegisterSound( "sound/feedback/shieldPierce.wav" ); + + //TiM + /*cgs.media.rewardImpressiveSound = trap_S_RegisterSound( "sound/voice/computer/misc/impressive.wav" ); + cgs.media.rewardExcellentSound = trap_S_RegisterSound( "sound/voice/computer/misc/excellent.wav" ); + cgs.media.rewardDeniedSound = trap_S_RegisterSound( "sound/voice/computer/misc/denied.wav" ); + cgs.media.rewardFirstStrikeSound = trap_S_RegisterSound( "sound/voice/computer/misc/1ststrike.wav"); + cgs.media.rewardAceSound = trap_S_RegisterSound( "sound/voice/computer/misc/ace.wav"); + cgs.media.rewardExpertSound = trap_S_RegisterSound( "sound/voice/computer/misc/expert.wav"); + cgs.media.rewardMasterSound = trap_S_RegisterSound( "sound/voice/computer/misc/master.wav"); + cgs.media.rewardChampionSound = trap_S_RegisterSound( "sound/voice/computer/misc/champion.wav");*/ + + //TiM + /*cgs.media.takenLeadSound = trap_S_RegisterSound( "sound/voice/computer/misc/takenlead.wav"); + cgs.media.tiedLeadSound = trap_S_RegisterSound( "sound/voice/computer/misc/tiedlead.wav"); + cgs.media.lostLeadSound = trap_S_RegisterSound( "sound/voice/computer/misc/lostlead.wav");*/ + + cgs.media.watrInSound = trap_S_RegisterSound( "sound/player/watr_in.wav"); + cgs.media.watrOutSound = trap_S_RegisterSound( "sound/player/watr_out.wav"); + cgs.media.watrUnSound = trap_S_RegisterSound( "sound/player/watr_un.wav"); + + cgs.media.jumpPadSound = trap_S_RegisterSound ("sound/items/damage3.wav" ); + + cgs.media.poweruprespawnSound = trap_S_RegisterSound ("sound/items/poweruprespawn.wav"); + cgs.media.disintegrateSound = trap_S_RegisterSound( "sound/weapons/prifle/disint.wav" ); + cgs.media.disintegrate2Sound = trap_S_RegisterSound( "sound/weapons/prifle/disint2.wav" ); + cgs.media.playerExplodeSound = trap_S_RegisterSound( "sound/weapons/explosions/fireball.wav" ); + + //TiM + /*cgs.media.holoInitSound = trap_S_RegisterSound("sound/voice/computer/misc/proginit.wav"); + cgs.media.holoDoorSound = trap_S_RegisterSound("sound/movers/doors/holoopen.wav"); + cgs.media.holoFadeSound = trap_S_RegisterSound("sound/movers/holodeckdecloak.wav");*/ + + cgs.media.phaserEmptySound = trap_S_RegisterSound("sound/weapons/phaser/phaserempty.wav"); + + //RPG-X: RedTechie - Load sound for shake cmd + cgs.media.ShakeSound = trap_S_RegisterSound("sound/shake.wav"); + + cgs.media.tedTextSound = trap_S_RegisterSound( "sound/interface/tedtext.wav" ); + + //RPG-X | Phenix | 13/02/2005 + for (i=0 ; i= GT_TEAM || cg_buildScript.integer ) { + cgs.media.teamRedShader = trap_R_RegisterShader( "sprites/team_red" ); + cgs.media.teamBlueShader = trap_R_RegisterShader( "sprites/team_blue" ); + cgs.media.redQuadShader = trap_R_RegisterShader("powerups/blueflag" ); + cgs.media.teamStatusBar = trap_R_RegisterShader( "gfx/2d/colorbar.tga" ); + }*/ + + cgs.media.chatShader = trap_R_RegisterShader( "sprites/chat" ); + + //cgs.media.bloodExplosionShader = trap_R_RegisterShader( "bloodExplosion" ); + + cgs.media.ringFlashModel = trap_R_RegisterModel("models/weaphits/ring02.md3"); + cgs.media.teleportEffectModel = trap_R_RegisterModel( "models/misc/telep.md3" ); + cgs.media.teleportEffectShader = trap_R_RegisterShader( "playerTeleport" ); + + //cgs.media.doorbox = trap_R_RegisterModel( "models/mapobjects/podium/hm_room.md3"); + + //RPG-X TiM : the bolton assets + cgs.media.phaserHolster = trap_R_RegisterModel( "models/boltOns/phaser_holster.md3"); + cgs.media.phaserHolsterInner = trap_R_RegisterModel( "models/boltOns/phaser_holster_inner.md3"); + + cgs.media.tricorderHolster = trap_R_RegisterModel( "models/boltOns/tricorder_holster.md3"); + cgs.media.tricorderHolsterInner = trap_R_RegisterModel( "models/boltOns/tricorder_holster_inner.md3"); + + //TiM : TR116 Eyescope + cgs.media.tr116EyeScope = trap_R_RegisterModel( "models/boltOns/tr116_scope.md3"); + //TiM : Flashlight + cgs.media.simsModule = trap_R_RegisterModel( "models/boltOns/sims_beacon.md3" ); + //EVA FPS Helmet + cgs.media.evaInterior = trap_R_RegisterModel( "models/boltOns/eva_interior.md3" ); + + cgs.media.shieldActivateShaderBlue = trap_R_RegisterShader( "gfx/misc/forcefield" ); //blue_portashield + cgs.media.shieldDamageShaderBlue = trap_R_RegisterShader( "gfx/misc/blue_dmgshield" ); + cgs.media.shieldActivateShaderRed = trap_R_RegisterShader( "gfx/misc/red_portashield" ); + cgs.media.shieldDamageShaderRed = trap_R_RegisterShader( "gfx/misc/red_dmgshield" ); + + cgs.media.weaponPlaceholderShader = trap_R_RegisterShader("powerups/placeholder" ); + cgs.media.rezOutShader = trap_R_RegisterShader("powerups/rezout"); + cgs.media.electricBodyShader = trap_R_RegisterShader("gfx/misc/electric"); + + /*cgs.media.medalImpressive = trap_R_RegisterShaderNoMip( "medal_impressive" ); + cgs.media.medalExcellent = trap_R_RegisterShaderNoMip( "medal_excellent" ); + cgs.media.medalFirstStrike = trap_R_RegisterShaderNoMip( "medal_firststrike" ); + cgs.media.medalAce = trap_R_RegisterShaderNoMip( "medal_ace" ); + cgs.media.medalExpert = trap_R_RegisterShaderNoMip( "medal_expert" ); + cgs.media.medalMaster = trap_R_RegisterShaderNoMip( "medal_master" ); + cgs.media.medalChampion = trap_R_RegisterShaderNoMip( "medal_champion" );*/ + + //RPG-X: RedTechie - Scoreboard Endcaps + cgs.media.scoreboardtopleft = trap_R_RegisterShaderNoMip( "menu/common/rpgx_sb_topleft"); + cgs.media.scoreboardtopright = trap_R_RegisterShaderNoMip( "menu/common/rpgx_sb_topright"); + cgs.media.scoreboardbotleft = trap_R_RegisterShaderNoMip( "menu/common/rpgx_sb_bottomleft"); + cgs.media.scoreboardbotright = trap_R_RegisterShaderNoMip( "menu/common/rpgx_sb_bottomright"); + + //RPG-X: RedTechie - Healthbar Curves + cgs.media.healthendcap = trap_R_RegisterShaderNoMip("gfx/interface/rpgx_healthbar_endcap"); + cgs.media.healthbigcurve = trap_R_RegisterShaderNoMip("gfx/interface/rpgx_healthbar_leftcorner"); + + //RPG-X: RedTechie - Cloak Sprite + cgs.media.cloakspriteShader = trap_R_RegisterShader("sprites/cloak"); + + cgs.media.scoreboardEndcap = trap_R_RegisterShaderNoMip( "menu/common/halfround_r_24"); + cgs.media.corner_12_24 = trap_R_RegisterShaderNoMip( "menu/common/corner_ll_24_12"); + cgs.media.corner_8_16_b = trap_R_RegisterShaderNoMip( "menu/common/corner_lr_8_16_b"); + + cgs.media.weaponcap1 = trap_R_RegisterShaderNoMip("gfx/interface/cap4"); + cgs.media.weaponcap2 = trap_R_RegisterShaderNoMip("gfx/interface/cap5"); + + cgs.media.weaponbox = trap_R_RegisterShaderNoMip("gfx/interface/weapon_box"); + cgs.media.weaponbox2 = trap_R_RegisterShaderNoMip("gfx/interface/weapon_box2"); + + memset( cg_items, 0, sizeof( cg_items ) ); + memset( cg_weapons, 0, sizeof( cg_weapons ) ); + + cg.loadLCARSStage = 5; // Loading bar stage 5 + //don't need a CG_LoadingString because there will be one in the LoadingItem() + + // only register the items that the server says we need + strcpy( items, CG_ConfigString( CS_ITEMS) ); + + for ( i = 1 ; i < bg_numItems ; i++ ) { + if ( items[ i ] == '1' || cg_buildScript.integer ) { + CG_LoadingItem( i ); + CG_RegisterItemVisuals( i ); + } + } + + // wall marks + cgs.media.holeMarkShader = trap_R_RegisterShader( "gfx/damage/hole_lg_mrk" ); + cgs.media.energyMarkShader = trap_R_RegisterShader( "gfx/damage/plasma_mrk" ); + cgs.media.shadowMarkShader = trap_R_RegisterShader( "markShadow" ); + cgs.media.wakeMarkShader = trap_R_RegisterShader( "wake" ); + + // Radar + //TiM: OPTIMIZATION: + //It's possible to use one set of textures, and use Alpha channels to vary + //the color thru the code... wouldn't that be better? + cgs.media.radarShader = trap_R_RegisterShader( "gfx/radar/radar" ); + /*cgs.media.rd_up = trap_R_RegisterShader( "gfx/radar/rd_up" ); + cgs.media.rd_down = trap_R_RegisterShader( "gfx/radar/rd_down" ); + cgs.media.rd_level = trap_R_RegisterShader( "gfx/radar/rd_level" ); + cgs.media.rd_red_up = trap_R_RegisterShader( "gfx/radar/rd_red_up" ); + cgs.media.rd_red_down = trap_R_RegisterShader( "gfx/radar/rd_red_down" ); + cgs.media.rd_red_level = trap_R_RegisterShader( "gfx/radar/rd_red_level" ); + cgs.media.rd_blue_up = trap_R_RegisterShader( "gfx/radar/rd_blue_up" ); + cgs.media.rd_blue_down = trap_R_RegisterShader( "gfx/radar/rd_blue_down" ); + cgs.media.rd_blue_level = trap_R_RegisterShader( "gfx/radar/rd_blue_level" ); + cgs.media.rd_white_up = trap_R_RegisterShader( "gfx/radar/rd_white_up" ); + cgs.media.rd_white_down = trap_R_RegisterShader( "gfx/radar/rd_white_down" ); + cgs.media.rd_white_level = trap_R_RegisterShader( "gfx/radar/rd_white_level" ); + cgs.media.rd_teal_up = trap_R_RegisterShader( "gfx/radar/rd_teal_up" ); + cgs.media.rd_teal_down = trap_R_RegisterShader( "gfx/radar/rd_teal_down" ); + cgs.media.rd_teal_level = trap_R_RegisterShader( "gfx/radar/rd_teal_level" ); + cgs.media.rd_black_up = trap_R_RegisterShader( "gfx/radar/rd_black_up" ); + cgs.media.rd_black_down = trap_R_RegisterShader( "gfx/radar/rd_black_down" ); + cgs.media.rd_black_level = trap_R_RegisterShader( "gfx/radar/rd_black_level" ); + cgs.media.rd_injured_up = trap_R_RegisterShader( "gfx/radar/injured_up" ); + cgs.media.rd_injured_down = trap_R_RegisterShader( "gfx/radar/injured_down" );*/ + cgs.media.rd_injured_level = trap_R_RegisterShader( "gfx/radar/injured_level" ); + + cgs.media.radarMain = trap_R_RegisterShaderNoMip( "gfx/radar/radar_icon" ); + + // register the inline models + cgs.numInlineModels = trap_CM_NumInlineModels(); + for ( i = 1 ; i < cgs.numInlineModels ; i++ ) { + char name[10]; + vec3_t mins, maxs; + int j; + + Com_sprintf( name, sizeof(name), "*%i", i ); + cgs.inlineDrawModel[i] = trap_R_RegisterModel( name ); + trap_R_ModelBounds( cgs.inlineDrawModel[i], mins, maxs ); + for ( j = 0 ; j < 3 ; j++ ) { + cgs.inlineModelMidpoints[i][j] = mins[j] + 0.5 * ( maxs[j] - mins[j] ); + } + } + + cg.loadLCARSStage = 6; // Loading bar stage 6 + CG_LoadingString( "Game Models" ); + + // register all the server specified models + for (i=1 ; i 0 ) { + cgs.locations = qtrue; + break; + } + } + + //TiM: Do the same for all tricorder string names + /*or ( i = 1; i < MAX_TRIC_STRINGS; i++ ) { + const char *strName; + + strName = CG_ConfigString( CS_TRIC_STRINGS+i ); + if ( !strName[0] ) { + break; + } + + //Com_Printf( S_COLOR_RED "USABLE MESSAGE IN CG: %s\n", strName ); + + //Com_sprintf( cgs.tricStrings[i], MAX_TOKEN_CHARS, "%s", strName ); + + //cgs.tricStrings[i] = (char *)strName; + //Com_Printf( S_COLOR_RED "%s\n", cgs.tricStrings[i] ); + Q_strncpyz( cgs.tricStrings[i], strName, MAX_TOKEN_CHARS ); + }*/ + + cg.loadLCARSStage = 7; // Loading bar stage 7 + CG_LoadingString( "Interface" ); + + // Registering interface graphics + for (i=0;i= MAX_CONFIGSTRINGS ) { + CG_Error( "CG_ConfigString: bad index: %i", index ); + } + return cgs.gameState.stringData + cgs.gameState.stringOffsets[ index ]; +} + +//================================================================== + +/* +====================== +CG_StartMusic + +====================== +*/ +void CG_StartMusic( void ) { + char *s; + char parm1[MAX_QPATH], parm2[MAX_QPATH]; + + // start the background music + s = (char *)CG_ConfigString( CS_MUSIC ); + Q_strncpyz( parm1, COM_Parse( &s ), sizeof( parm1 ) ); + Q_strncpyz( parm2, COM_Parse( &s ), sizeof( parm2 ) ); + + trap_S_StartBackgroundTrack( parm1, parm2 ); +} + +extern int altAmmoUsage[]; +void CG_InitModRules( void ) +{ + if ( cgs.pModDisintegration ) + {//don't use up ammo in disintegration mode + altAmmoUsage[WP_COMPRESSION_RIFLE] = 0; + } + if ( cgs.pModSpecialties ) + {//tripwires use more ammo + altAmmoUsage[WP_GRENADE_LAUNCHER] = 3; + } +} + +/* +================= +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 ) { + const char *s; + + // clear everything + memset( &cgs, 0, sizeof( cgs ) ); + memset( &cg, 0, sizeof( cg ) ); + memset( cg_entities, 0, sizeof(cg_entities) ); + memset( cg_weapons, 0, sizeof(cg_weapons) ); + memset( cg_items, 0, sizeof(cg_items) ); + + //Flush Sound Effects + memset( &cg_animsSndList, 0, sizeof( cg_animsSndList ) ); + + cgs.processedSnapshotNum = serverMessageNum; + cgs.serverCommandSequence = serverCommandSequence; + + CG_LoadIngameText(); + + CG_LoadFonts(); + + // Loading graphics + cgs.media.loading1 = trap_R_RegisterShaderNoMip( "menu/loading/smpiece1.tga" ); + cgs.media.loading2 = trap_R_RegisterShaderNoMip( "menu/loading/smpiece2.tga" ); + cgs.media.loading3 = trap_R_RegisterShaderNoMip( "menu/loading/smpiece3.tga" ); + cgs.media.loading4 = trap_R_RegisterShaderNoMip( "menu/loading/smpiece4.tga" ); + cgs.media.loading5 = trap_R_RegisterShaderNoMip( "menu/loading/smpiece5.tga" ); + cgs.media.loading6 = trap_R_RegisterShaderNoMip( "menu/loading/smpiece6.tga" ); + cgs.media.loading7 = trap_R_RegisterShaderNoMip( "menu/loading/smpiece7.tga" ); + cgs.media.loading8 = trap_R_RegisterShaderNoMip( "menu/loading/smpiece8.tga" ); + cgs.media.loading9 = trap_R_RegisterShaderNoMip( "menu/loading/smpiece9.tga" ); + cgs.media.loadingcircle = trap_R_RegisterShaderNoMip( "menu/loading/arrowpiece.tga" ); + cgs.media.loadingquarter= trap_R_RegisterShaderNoMip( "menu/loading/quarter.tga" ); + cgs.media.loadingcorner = trap_R_RegisterShaderNoMip( "menu/common/corner_lr_8_16.tga" ); + cgs.media.loadingtrim = trap_R_RegisterShaderNoMip( "menu/loading/trimupper.tga" ); + cgs.media.circle = trap_R_RegisterShaderNoMip( "menu/common/circle.tga" ); + cgs.media.circle2 = trap_R_RegisterShaderNoMip( "menu/objectives/circle_out.tga" ); + cgs.media.corner_12_18 = trap_R_RegisterShaderNoMip( "menu/common/corner_ll_12_18.tga" ); + cgs.media.halfroundr_22 = trap_R_RegisterShaderNoMip( "menu/common/halfroundr_22.tga" ); + + cgs.media.corner_ul_20_30= trap_R_RegisterShaderNoMip( "menu/common/corner_ul_20_30.tga" ); + cgs.media.corner_ll_8_30= trap_R_RegisterShaderNoMip( "menu/common/corner_ll_8_30.tga" ); + + cg.loadLCARSStage = 0; + cg.loadLCARScnt = 0; + // load a few needed things before we do any screen updates + cgs.media.charsetShader = trap_R_RegisterShaderNoMip( "gfx/2d/charsgrid_med" ); + cgs.media.whiteShader = trap_R_RegisterShader( "white" ); + cgs.media.white2Shader = trap_R_RegisterShader( "white2" ); + cgs.media.charsetPropTiny = trap_R_RegisterShaderNoMip("gfx/2d/chars_tiny"); + cgs.media.charsetProp = trap_R_RegisterShaderNoMip("gfx/2d/chars_medium"); + cgs.media.charsetPropBig = trap_R_RegisterShaderNoMip("gfx/2d/chars_big"); +// cgs.media.charsetPropGlow = trap_R_RegisterShaderNoMip( "menu/art/font1_prop_glo.tga" ); + cgs.media.charsetPropB = trap_R_RegisterShaderNoMip( "gfx/2d/chars_medium.tga" ); + + CG_RegisterCvars(); + + CG_InitConsoleCommands(); + + BG_LoadItemNames(); + + cg.weaponSelect = WP_PHASER; + + cgs.redflag = cgs.blueflag = -1; // For compatibily, default to unset for + // old servers + + // get the rendering configuration from the client system + trap_GetGlconfig( &cgs.glconfig ); + cgs.screenXScale = cgs.glconfig.vidWidth / 640.0; + cgs.screenYScale = cgs.glconfig.vidHeight / 480.0; + + // get the gamestate from the client system + trap_GetGameState( &cgs.gameState ); + + // check version + s = CG_ConfigString( CS_GAME_VERSION ); + if ( strcmp( s, GAME_VERSION ) ) { + CG_Error( "Client/Server game mismatch: %s/%s", GAME_VERSION, s ); + } + + s = CG_ConfigString( CS_LEVEL_START_TIME ); + cgs.levelStartTime = atoi( s ); + + CG_ParseServerinfo(); + + // load the new map + CG_LoadingString( "collision map" ); + + trap_CM_LoadMap( cgs.mapname ); + + cg.loading = qtrue; // force players to load instead of defer + + CG_LoadObjectivesForMap(); + + CG_RegisterSounds(); + + CG_RegisterGraphics(); + + CG_RegisterClients(); // if low on memory, some clients will be deferred + + cg.loading = qfalse; // future players will be deferred + + CG_InitLocalEntities(); + + CG_InitMarkPolys(); + + // remove the last loading update + cg.infoScreenText[0] = 0; + + // Make sure we have update values (scores) + CG_SetConfigValues(); + + CG_StartMusic(); + + CG_LoadingString( "" ); + + // To get the interface timing started + cg.interfaceStartupTime = 0; + cg.interfaceStartupDone = 0; + + CG_InitModRules(); + + if ( !CG_LoadCrosshairs() ) { + CG_Error( "Couldn't load crosshairs script" ); + } + + if ( !CG_LoadRanks() ) { + CG_Error( "Couldn't load rankset script: %s", cgs.rankSet ); + } +} + +/* +================= +CG_Shutdown + +Called before every level change or subsystem restart +================= +*/ +void CG_Shutdown( void ) { + // some mods may need to do cleanup work here, + // like closing files or archiving session data + trap_Cvar_Set ("rpg_playIntro", "0"); +} + + +#define MAXINGAMETEXT 5000 +char ingameText[MAXINGAMETEXT]; + +/* +================= +CG_ParseIngameText +================= +*/ +void CG_ParseIngameText(void) +{ + char *token; + char *buffer; + int i; + int len; + + COM_BeginParseSession(); + + buffer = ingameText; + i = 1; // Zero is null string + while ( buffer ) + { + token = COM_ParseExt( &buffer, qtrue ); + + len = strlen(token); + if (len) + { + ingame_text[i] = (buffer - (len + 1)); // The +1 is to get rid of the " at the beginning of the sting. + *(buffer - 1) = '\0'; // Place an string end where is belongs. + + ++i; + } + + if (i> IGT_MAX) + { + Com_Printf( S_COLOR_RED "CG_ParseIngameText : too many values!\n"); + return; + } + } + + if (i != IGT_MAX) + { + Com_Printf( S_COLOR_RED "CG_ParseIngameText : not enough lines! Read %d of %d!\n",i,IGT_MAX); + for(;i MAXINGAMETEXT) + { + Com_Printf( S_COLOR_RED "CG_LoadIngameText : mp_ingametext.dat file bigger than %d!\n",MAXINGAMETEXT); + return; + } + + // initialise the data area + memset(ingameText, 0, sizeof(ingameText)); + + trap_FS_Read( ingameText, len, f ); + + trap_FS_FCloseFile( f ); + + + CG_ParseIngameText(); + +} + +/* +================= +CG_LoadObjectivesForMap +================= +*/ +void CG_LoadObjectivesForMap(void) +{ + int len, objnum = 0; + char *token; + char *buf; + fileHandle_t f; + char fileName[MAX_QPATH]; + char fullFileName[MAX_QPATH]; + char objtext[MAX_OBJ_TEXT_LENGTH]; + + COM_StripExtension( cgs.mapname, fileName ); + CG_LanguageFilename( fileName, "efo", fullFileName); + + len = trap_FS_FOpenFile( fullFileName, &f, FS_READ ); + + if ( len > MAX_OBJ_TEXT_LENGTH ) + { + Com_Printf( S_COLOR_RED "CG_LoadObjectivesForMap : %s file bigger than %d!\n", fileName, MAX_OBJ_TEXT_LENGTH ); + return; + } + + trap_FS_Read( objtext, len, f ); + + trap_FS_FCloseFile( f ); + + buf = objtext; + //Now parse out each objective + while ( 1 ) + { + token = COM_ParseExt( &buf, qtrue ); + if ( !token[0] ) { + break; + } + + // Normal objective text + if ( !Q_strncmp( token, "obj", 3 ) ) + { + objnum = atoi( &token[3] ); + + if ( objnum < 1 || objnum == MAX_OBJECTIVES ) { + Com_Printf( "Invalid objective number (%d), valid range is 1 to %d\n", objnum, MAX_OBJECTIVES ); + break; + } + + //Now read the objective text into the current objective + token = COM_ParseExt( &buf, qfalse ); + Q_strncpyz( cgs.objectives[objnum-1].text, token, sizeof(cgs.objectives[objnum-1].text) ); + } + + else if ( !Q_strncmp( token, "abridged_obj", 12 ) ) + { + objnum = atoi( &token[12] ); + + if ( objnum < 1 || objnum == MAX_OBJECTIVES ) + { + Com_Printf( "Invalid objective number (%d), valid range is 1 to %d\n", objnum, MAX_OBJECTIVES ); + break; + } + + //Now read the objective text into the current objective + token = COM_ParseExt( &buf, qfalse ); + Q_strncpyz( cgs.objectives[objnum-1].abridgedText, token, sizeof(cgs.objectives[objnum-1].abridgedText) ); + } + } +} + + diff --git a/cgame/cg_marks.c b/cgame/cg_marks.c new file mode 100644 index 0000000..6e9ad29 --- /dev/null +++ b/cgame/cg_marks.c @@ -0,0 +1,277 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_marks.c -- wall marks + +#include "cg_local.h" + +/* +=================================================================== + +MARK POLYS + +=================================================================== +*/ + + +markPoly_t cg_activeMarkPolys; // double linked list +markPoly_t *cg_freeMarkPolys; // single linked list +markPoly_t cg_markPolys[MAX_MARK_POLYS]; + +/* +=================== +CG_InitMarkPolys + +This is called at startup and for tournement restarts +=================== +*/ +void CG_InitMarkPolys( void ) { + int i; + + memset( cg_markPolys, 0, sizeof(cg_markPolys) ); + + cg_activeMarkPolys.nextMark = &cg_activeMarkPolys; + cg_activeMarkPolys.prevMark = &cg_activeMarkPolys; + cg_freeMarkPolys = cg_markPolys; + for ( i = 0 ; i < MAX_MARK_POLYS - 1 ; i++ ) { + cg_markPolys[i].nextMark = &cg_markPolys[i+1]; + } +} + + +/* +================== +CG_FreeMarkPoly +================== +*/ +void CG_FreeMarkPoly( markPoly_t *le ) { + if ( !le->prevMark ) { + CG_Error( "CG_FreeLocalEntity: not active" ); + } + + // remove from the doubly linked active list + le->prevMark->nextMark = le->nextMark; + le->nextMark->prevMark = le->prevMark; + + // the free list is only singly linked + le->nextMark = cg_freeMarkPolys; + cg_freeMarkPolys = le; +} + +/* +=================== +CG_AllocMark + +Will allways succeed, even if it requires freeing an old active mark +=================== +*/ +markPoly_t *CG_AllocMark( void ) { + markPoly_t *le; + int time; + + if ( !cg_freeMarkPolys ) { + // no free entities, so free the one at the end of the chain + // remove the oldest active entity + time = cg_activeMarkPolys.prevMark->time; + while (cg_activeMarkPolys.prevMark && time == cg_activeMarkPolys.prevMark->time) { + CG_FreeMarkPoly( cg_activeMarkPolys.prevMark ); + } + } + + le = cg_freeMarkPolys; + cg_freeMarkPolys = cg_freeMarkPolys->nextMark; + + memset( le, 0, sizeof( *le ) ); + + // link into the active list + le->nextMark = cg_activeMarkPolys.nextMark; + le->prevMark = &cg_activeMarkPolys; + cg_activeMarkPolys.nextMark->prevMark = le; + cg_activeMarkPolys.nextMark = le; + return le; +} + + + +/* +================= +CG_ImpactMark + +origin should be a point within a unit of the plane +dir should be the plane normal + +temporary marks will not be stored or randomly oriented, but immediately +passed to the renderer. +================= +*/ +#define MAX_MARK_FRAGMENTS 128 +#define MAX_MARK_POINTS 384 + +void CG_ImpactMark( qhandle_t markShader, const vec3_t origin, const vec3_t dir, + float orientation, float red, float green, float blue, float alpha, + qboolean alphaFade, float radius, qboolean temporary ) { + vec3_t axis[3]; + float texCoordScale; + vec3_t originalPoints[4]; + byte colors[4]; + int i, j; + int numFragments; + markFragment_t markFragments[MAX_MARK_FRAGMENTS], *mf; + vec3_t markPoints[MAX_MARK_POINTS]; + vec3_t projection; + +#ifdef _DEBUG + if (!markShader) + { + Com_Printf("CG_ImpactMark: NULL shader\n"); + } +#endif + + if ( !cg_addMarks.integer ) { + return; + } + + if ( radius <= 0 ) { + //CG_Error( "CG_ImpactMark called with <= 0 radius" ); + return; + } + + // create the texture axis + VectorNormalize2( dir, axis[0] ); + PerpendicularVector( axis[1], axis[0] ); + RotatePointAroundVector( axis[2], axis[0], axis[1], orientation ); + CrossProduct( axis[0], axis[2], axis[1] ); + + texCoordScale = 0.5 * 1.0 / radius; + + // create the full polygon + for ( i = 0 ; i < 3 ; i++ ) { + originalPoints[0][i] = origin[i] - radius * axis[1][i] - radius * axis[2][i]; + originalPoints[1][i] = origin[i] + radius * axis[1][i] - radius * axis[2][i]; + originalPoints[2][i] = origin[i] + radius * axis[1][i] + radius * axis[2][i]; + originalPoints[3][i] = origin[i] - radius * axis[1][i] + radius * axis[2][i]; + } + + // get the fragments + VectorScale( dir, -20, projection ); + numFragments = trap_CM_MarkFragments( 4, (void *)originalPoints, + projection, MAX_MARK_POINTS, markPoints[0], + MAX_MARK_FRAGMENTS, markFragments ); + + colors[0] = red * 255; + colors[1] = green * 255; + colors[2] = blue * 255; + colors[3] = alpha * 255; + + for ( i = 0, mf = markFragments ; i < numFragments ; i++, mf++ ) { + polyVert_t *v; + polyVert_t verts[MAX_VERTS_ON_POLY]; + markPoly_t *mark; + + // we have an upper limit on the complexity of polygons + // that we store persistantly + if ( mf->numPoints > MAX_VERTS_ON_POLY ) { + mf->numPoints = MAX_VERTS_ON_POLY; + } + for ( j = 0, v = verts ; j < mf->numPoints ; j++, v++ ) { + vec3_t delta; + + VectorCopy( markPoints[mf->firstPoint + j], v->xyz ); + + VectorSubtract( v->xyz, origin, delta ); + v->st[0] = 0.5 + DotProduct( delta, axis[1] ) * texCoordScale; + v->st[1] = 0.5 + DotProduct( delta, axis[2] ) * texCoordScale; + *(int *)v->modulate = *(int *)colors; + } + + // if it is a temporary (shadow) mark, add it immediately and forget about it + if ( temporary ) { + trap_R_AddPolyToScene( markShader, mf->numPoints, verts ); + continue; + } + + // otherwise save it persistantly + mark = CG_AllocMark(); + mark->time = cg.time; + mark->alphaFade = alphaFade; + mark->markShader = markShader; + mark->poly.numVerts = mf->numPoints; + mark->color[0] = red; + mark->color[1] = green; + mark->color[2] = blue; + mark->color[3] = alpha; + memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[0] ) ); + } +} + + +/* +=============== +CG_AddMarks +=============== +*/ +#define MARK_TOTAL_TIME 500000 +#define MARK_FADE_TIME 50000 +#define MARK_DIV_3000 1.0/3000.0 + +void CG_AddMarks( void ) { + int j; + markPoly_t *mp, *next; + int t; + int fade; + + if ( !cg_addMarks.integer ) { + return; + } + + mp = cg_activeMarkPolys.nextMark; + for ( ; mp != &cg_activeMarkPolys ; mp = next ) { + // grab next now, so if the local entity is freed we + // still have it + next = mp->nextMark; + + // see if it is time to completely remove it + if ( cg.time > mp->time + MARK_TOTAL_TIME ) { + CG_FreeMarkPoly( mp ); + continue; + } + + // fade out the energy bursts + if ( mp->markShader == cgs.media.energyMarkShader ) { + + fade = 450 - 450 * ( (cg.time - mp->time ) * MARK_DIV_3000 ); + if ( fade < 255 ) { + if ( fade < 0 ) { + fade = 0; + } + if ( mp->verts[0].modulate[0] != 0 ) { + for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { + mp->verts[j].modulate[0] = mp->color[0] * fade; + mp->verts[j].modulate[1] = mp->color[1] * fade; + mp->verts[j].modulate[2] = mp->color[2] * fade; + } + } + } + } + + // fade all marks out with time + t = mp->time + MARK_TOTAL_TIME - cg.time; + if ( t < MARK_FADE_TIME ) { + fade = 255 * t / MARK_FADE_TIME; + if ( mp->alphaFade ) { + for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { + mp->verts[j].modulate[3] = fade; + } + } else { + for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { + mp->verts[j].modulate[0] = mp->color[0] * fade; + mp->verts[j].modulate[1] = mp->color[1] * fade; + mp->verts[j].modulate[2] = mp->color[2] * fade; + } + } + } + + + trap_R_AddPolyToScene( mp->markShader, mp->poly.numVerts, mp->verts ); + } +} + diff --git a/cgame/cg_motionblur.c b/cgame/cg_motionblur.c new file mode 100644 index 0000000..40bcd4b --- /dev/null +++ b/cgame/cg_motionblur.c @@ -0,0 +1,84 @@ +#include "cg_local.h" + +#define MAX_MOTIONBLURDOTS 20 + +typedef struct motionblurDot_s { + qboolean active; + refEntity_t refEnt; + int startTime; + int lifeTime; +} motionblurDot_t; + +//static motionblurDot_t cg_motionblurDots[MAX_MOTIONBLURDOTS]; + + + +void CG_MotionBlur(void) { + //motionblurDot_t *dot; + //vec3_t pos, axis[3]; + //int i; + + + /*if ( !cg.snap->ps.powerups[PW_BOOST] && cg.snap->ps.timers[tmZanzoken] < 1 && !cg.snap->ps.timers[tmTransform]) { + cg.refdef.rdflags &= ~RDF_MOTIONBLUR; + + + //for ( i = 0; i < MAX_MOTIONBLURDOTS; i++ ) { + // cg_motionblurDots[i].active = qfalse; + //} + + + return; + }*/ + + + cg.refdef.rdflags |= RDF_MOTIONBLUR; + + /* + // Destroy dots over lifetime + for ( i = 0; i < MAX_MOTIONBLURDOTS; i++ ) { + dot = &cg_motionblurDots[i]; + + if ( dot->lifeTime + dot->startTime < cg.time ) { + dot->active = qfalse; + } + } + + // Create new dots + for ( i = 0; i < MAX_MOTIONBLURDOTS; i++ ) { + dot = &cg_motionblurDots[i]; + + if ( dot->active ) + continue; + + VectorCopy( cg.predictedPlayerEntity.lerpOrigin, pos ); + VectorNormalize2( cg.predictedPlayerState.velocity, axis[0] ); + VectorMA( pos, 300, axis[0], pos ); + RotateAroundDirection( axis, crandom() * 360 ); + VectorMA( pos, 120, axis[2], pos ); + + memset( &(dot->refEnt), 0, sizeof(refEntity_t)); + dot->refEnt.reType = RT_SPRITE; + dot->refEnt.radius = 2; + dot->refEnt.customShader = cgs.media.whiteShader; + dot->refEnt.shaderRGBA[0] = 255; + dot->refEnt.shaderRGBA[1] = 255; + dot->refEnt.shaderRGBA[2] = 255; + dot->refEnt.shaderRGBA[3] = 128; + VectorCopy( pos, dot->refEnt.origin ); + + dot->lifeTime = 250 + crandom() * 100; + dot->startTime = cg.time + crandom() * 150; + dot->active = qtrue; + } + + // Render dots + for ( i = 0; i < MAX_MOTIONBLURDOTS; i++ ) { + dot = &cg_motionblurDots[i]; + if ( dot->startTime > cg.time ) + continue; + + trap_R_AddRefEntityToScene( &(dot->refEnt)); + } + */ +} diff --git a/cgame/cg_players.c b/cgame/cg_players.c new file mode 100644 index 0000000..ce0b839 --- /dev/null +++ b/cgame/cg_players.c @@ -0,0 +1,5314 @@ +// +// cg_players.c -- handle the media and animation for player entities + +#define cg_players_c //RPG-X: J2J - Added to help solve LNK2005 errors (special case for cg_players.c) + +#include "cg_local.h" +#include "cg_screenfx.h" +#include "fx_local.h" +//#include "cg_anims.h" //RPG-X: J2J - Added for animation string table. + +const char *cg_customSoundNames[MAX_CUSTOM_SOUNDS] = { + "*death1.wav", + "*death2.wav", + "*death3.wav", + "*jump1.wav", + "*pain25.wav", + "*pain50.wav", + "*pain75.wav", + "*pain100.wav", + "*falling1.wav", + "*gasp.wav", + "*drown.wav", + "*fall1.wav", + "*taunt1.wav", + "*taunt2.wav", + "*taunt3.wav", + "*taunt4.wav", + "*taunt5.wav" +}; + +stringID_table_t BoltonTable[BOLTON_MAX + 1] = +{ + ENUM2STRING(BOLTON_HEAD), + ENUM2STRING(BOLTON_TORSO), + ENUM2STRING(BOLTON_LEGS), + NULL, -1 +}; + +int timeParam; +//int beamTimeParam; //RPG-X : TiM - Beaming + +int entNum; +extern char* BG_RegisterRace( const char *name ); + +/* +================ +CG_CustomSound + +================ +*/ +sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ) { + clientInfo_t *ci; + int i; + + if ( soundName[0] != '*' ) { + return trap_S_RegisterSound( soundName ); + } + + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + clientNum = 0; + } + ci = &cgs.clientinfo[ clientNum ]; + + for ( i = 0 ; i < MAX_CUSTOM_SOUNDS && cg_customSoundNames[i] ; i++ ) { + if ( !strcmp( soundName, cg_customSoundNames[i] ) ) { + return ci->sounds[i]; + } + } + + CG_Error( "Unknown custom sound: %s", soundName ); + return 0; +} + +/* +============================================================================= + +ANIM SOUND CONFIG LOADING AND PLAYING + +============================================================================= +*/ + +/* +void CG_PlayerAnimSounds( animsounds_t *animSounds, int frame, const vec3_t org, int entNum ) + +play any keyframed sounds - only when start a new frame +This func is called once for legs and once for torso +*/ +//void CG_PlayerAnimSounds( animsounds_t *animSounds, int frame, int entNum, qboolean metal ) +void CG_PlayerAnimSounds( animsounds_t *animSounds, int frame, int entNum, int surfType) +{ + int i; + int holdSnd = -1; + qboolean playSound = qfalse; + + /*if ( entNum == cg.predictedPlayerState.clientNum && !cg.renderingThirdPerson ) + {//player in first person view does not play any keyframed sounds + return; + }*/ + + // Check for anim sound + for (i=0;i irandom(0, 99) ) + { + playSound = qtrue; + } + break; + } + } + + // Play sound + if (holdSnd != -1 && playSound) + { + if (holdSnd != 0) // 0 = default sound, ie file was missing + { + trap_S_StartSound( NULL, entNum, CHAN_BODY, holdSnd ); //CHAN_AUTO + } + + } +} + +void ParseAnimationSndBlock(const char *filename, animsounds_t *animSounds, animation_t *animations, int *i,char **text_p) +{ + char *token; + char soundString[MAX_QPATH]; + int lowestVal, highestVal; + int animNum, num, n; + + // get past starting bracket + while(1) + { + token = COM_Parse( text_p ); + if ( !Q_stricmp( token, "{" ) ) + { + break; + } + } + + animSounds += *i; + + // read information for each frame + while ( 1 ) + { + // Get base frame of sequence + token = COM_Parse( text_p ); + if ( !token || !token[0]) + { + break; + } + + if ( !Q_stricmp( token, "}" ) ) // At end of block + { + break; + } + + //Compare to same table as animations used + // so we don't have to use actual numbers for animation first frames, + // just need offsets. + //This way when animation numbers change, this table won't have to be updated, + // at least not much. + animNum = GetIDForString(animTable, token); + if(animNum == -1) + {//Unrecognized ANIM ENUM name, or we're skipping this line, keep going till you get a good one + Com_Printf(S_COLOR_YELLOW"WARNING: Unknown token %s in animSound file %s\n", token, filename ); + continue; + } + + if ( animations[animNum].numFrames == 0 ) + {//we don't use this anim + Com_Printf(S_COLOR_YELLOW"WARNING: %s animsounds.cfg: anim %s not used by this model\n", filename, token); + + // Get offset to frame within sequence + token = COM_Parse( text_p ); + //get soundstring + token = COM_Parse( text_p ); + //get lowest value + token = COM_Parse( text_p ); + //get highest value + token = COM_Parse( text_p ); + //get probability + token = COM_Parse( text_p ); + + continue; + } + + animSounds->keyFrame = animations[animNum].firstFrame; + + // Get offset to frame within sequence + token = COM_Parse( text_p ); + if ( !token ) + { + break; + } + animSounds->keyFrame += atoi( token ); + + //get soundstring + token = COM_Parse( text_p ); + if ( !token ) + { + break; + } + strcpy(soundString, token); + + //get lowest value + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + lowestVal = atoi( token ); + + //get highest value + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + highestVal = atoi( token ); + + //Now precache all the sounds + //NOTE: If we can be assured sequential handles, we can store sound indices + // instead of strings, unfortunately, if these sounds were previously + // registered, we cannot be guaranteed sequential indices. Thus an array + if(lowestVal && highestVal) + { + for ( n = lowestVal, num = 0; n <= highestVal && num < MAX_RANDOM_ANIMSOUNDS; n++, num++ ) + { + animSounds->soundIndex[num] = trap_S_RegisterSound( va( soundString, n ) ); + } + animSounds->numRandomAnimSounds = num - 1; + } + else + { + animSounds->soundIndex[0] = trap_S_RegisterSound( va( soundString ) ); +#ifndef FINAL_BUILD + if ( !animSounds->soundIndex[0] ) + {//couldn't register it - file not found + Com_Printf( S_COLOR_RED "ParseAnimationSndBlock: sound %s does not exist (animsound.cfg %s)!\n", soundString, filename ); + } +#endif + animSounds->numRandomAnimSounds = 0; + } + + + //get probability + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + + animSounds->probability = atoi( token ); + ++animSounds; + ++*i; + } +} + + +/* +====================== +CG_ParseAnimationSndFile + +Read a configuration file containing animation sounds +models/players/munro/animsounds.cfg, etc + +This file's presence is not required + +====================== +*/ +static int CG_ParseAnimationSndFile( const char *filename, int animFileIndex ) +{ + char *text_p; + int len; + char *token; + char text[20000]; + fileHandle_t f; + int i, j, upper_i, lower_i; + animsounds_t *lowerAnimSounds; + animsounds_t *upperAnimSounds; + animation_t *animations; + + /*if ( knownAnimFileSets[animFileIndex].soundsCached ) + {//already cached this one + return; + }*/ + + for ( i = 0; i < cg_numSndAnims; i++ ) { + if ( !Q_stricmp( filename, cg_animsSndList[i].animSndFileRoute ) ) { + return i; + } + } + + //Mark this anim set so that we know we tried to load he sounds, don't care if the load failed + //knownAnimFileSets[animFileIndex].soundsCached = qtrue; + + animations = cg_animsList[animFileIndex].animations; + lowerAnimSounds = cg_animsSndList[cg_numSndAnims].lowerAnimSounds; + upperAnimSounds = cg_animsSndList[cg_numSndAnims].upperAnimSounds; + + //initialize anim sound array + for(i = 0; i < MAX_ANIM_SOUNDS; i++) + { + upperAnimSounds[i].numRandomAnimSounds = 0; + lowerAnimSounds[i].numRandomAnimSounds = 0; + for(j = 0; j < MAX_RANDOM_ANIMSOUNDS; j++) + { + upperAnimSounds[i].soundIndex[j] = -1; + lowerAnimSounds[i].soundIndex[j] = -1; + } + } + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( len <= 0 ) + {//no file + return -1; + } + if ( len >= sizeof( text ) - 1 ) + { + CG_Printf( "File %s too long\n", filename ); + return -1; + } + + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + upper_i =0; + lower_i =0; + + // read information for batches of sounds (UPPER or LOWER) + while ( 1 ) + { + // Get base frame of sequence + token = COM_Parse( &text_p ); + if ( !token || !token[0] ) + { + break; + } + + if ( !Q_stricmp(token,"UPPERSOUNDS") ) // A batch of upper sounds + { + ParseAnimationSndBlock( filename, upperAnimSounds, animations, &upper_i, &text_p ); + } + + else if ( !Q_stricmp(token,"LOWERSOUNDS") ) // A batch of lower sounds + { + ParseAnimationSndBlock( filename, lowerAnimSounds, animations, &lower_i, &text_p ); + } + } + + i = cg_numSndAnims; + cg_numSndAnims++; + + return i; +} + +/* +============================================================================= + +MODEL SCRIPT LOADING + +============================================================================= +*/ + +/* +====================== +CG_ParseAnimationFile + +Read a configuration file containing animation coutns and rates +models/players_rpgx/munro/animation.cfg, etc + +TiM: Small modification. Based on the nuber of animations parsed, +this will return an index to the cell in the global animation array +where the relevant animation data is kept. Based on both the JKA concept and the EF method +of caching assets, this is far more efficient since it means that if two people use models +with the same body models using the same animation data, the anim data will ony need be loaded once :) + +A lot more efficient considering how many freakin more animations we introduced with this model system lol. +====================== +*/ +// +//static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) { +static int CG_ParseAnimationFile( const char* filename/*, clientInfo_t *ci*/ ) { + char *text_p, *prev; + int len; + int i; + char *token; + float fps; + int skip; + char text[20000]; + fileHandle_t f; + animation_t *animations; + + //CG_Printf( "Anim is %s\n", filename ); + + if ( cg_numAnims > 0 ) { + for ( i = 0; i <= cg_numAnims; i++ ) { + if ( !Q_stricmpn( cg_animsList[i].animFileRoute, filename, (int)strlen( filename ) ) ) { //We found a matching anim set + //Com_Printf( S_COLOR_RED "Using index: %i\n", i ); + return i; + } + } + } + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( len <= 0 ) { + CG_Printf( S_COLOR_RED "File %s not found\n", filename ); + return -1; //qfalse + } + if ( len >= sizeof( text ) - 1 ) { + CG_Printf( S_COLOR_RED "File %s too long\n", filename ); + return -1; //qfalse + } + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + + //animations = ci->animations; + animations = cg_animsList[cg_numAnims].animations; + + //copy the file name to the gloabl anims array. It doesn't matter + //if it returns false, since the same cell will be flushed on the next call then. + memset( cg_animsList[cg_numAnims].animFileRoute, 0, MAX_QPATH ); + Q_strncpyz( cg_animsList[cg_numAnims].animFileRoute, filename, MAX_QPATH ); + + //flush the anims + memset( animations, 0, sizeof( animations ) ); + + // parse the text + text_p = text; + skip = 0; // quite the compiler warning + + /* + ci->footsteps = FOOTSTEP_NORMAL; + VectorClear( ci->headOffset ); + ci->gender = GENDER_MALE; + + Q_strncpyz(ci->soundPath, ci->modelName, sizeof(ci->soundPath));*/ + + // read optional parameters + while ( 1 ) { + prev = text_p; // so we can unget + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + /*if ( !Q_stricmp( token, "footsteps" ) ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + if ( !Q_stricmp( token, "default" ) || !Q_stricmp( token, "normal" ) ) { + ci->footsteps = FOOTSTEP_NORMAL; + } else if ( !Q_stricmp( token, "borg" ) ) { + ci->footsteps = FOOTSTEP_BORG; + } else if ( !Q_stricmp( token, "reaver" ) ) { + ci->footsteps = FOOTSTEP_REAVER; + } else if ( !Q_stricmp( token, "species" ) ) { + ci->footsteps = FOOTSTEP_SPECIES; + } else if ( !Q_stricmp( token, "warbot" ) ) { + ci->footsteps = FOOTSTEP_WARBOT; + } else if ( !Q_stricmp( token, "boot" ) ) { + ci->footsteps = FOOTSTEP_BOOT; + } else if ( !Q_stricmp( token, "flesh" ) ) { // Old Q3 defaults, for compatibility. -PJL + ci->footsteps = FOOTSTEP_SPECIES; + } else if ( !Q_stricmp( token, "mech" ) ) { // Ditto + ci->footsteps = FOOTSTEP_BORG; + } else if ( !Q_stricmp( token, "energy" ) ) { // Ditto + ci->footsteps = FOOTSTEP_BORG; + } else { + CG_Printf( "Bad footsteps parm in %s: %s\n", filename, token ); + } + continue; + } else if ( !Q_stricmp( token, "headoffset" ) ) { + for ( i = 0 ; i < 3 ; i++ ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + ci->headOffset[i] = atof( token ); + } + continue; + } else if ( !Q_stricmp( token, "sex" ) ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + if ( token[0] == 'f' || token[0] == 'F' ) { + ci->gender = GENDER_FEMALE; + } else if ( token[0] == 'n' || token[0] == 'N' ) { + ci->gender = GENDER_NEUTER; + } else { + ci->gender = GENDER_MALE; + } + continue; + } else if ( !Q_stricmp( token, "soundpath" ) ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + Q_strncpyz(ci->soundPath,token,sizeof (ci->soundPath) ); + continue; + } */ + + // if it is a number, start parsing animations + if ( token[0] >= '0' && token[0] <= '9' ) { + text_p = prev; // unget the token + break; + } + Com_Printf( "unknown token '%s' is %s\n", token, filename ); + } + + // read information for each frame + for ( i = 0 ; i < MAX_ANIMATIONS ; i++ ) { + + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + animations[i].firstFrame = atoi( token ); + // leg only frames are adjusted to not count the upper body only frames + if ( i == LEGS_KNEEL1 ) { //LEGS_WALKCR + skip = animations[LEGS_KNEEL1].firstFrame - animations[TORSO_ACTIVATEMEDKIT1].firstFrame; //TORSO_GESTURE + } + if ( i >= LEGS_KNEEL1 ) { + animations[i].firstFrame -= skip; + } + + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + animations[i].numFrames = atoi( token ); + + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + animations[i].loopFrames = atoi( token ); + + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + fps = atof( token ); + if ( fps == 0 ) { + fps = 1; + } + animations[i].frameLerp = 1000 / fps; + animations[i].initialLerp = 1000 / fps; + } + + if ( i != MAX_ANIMATIONS ) { + CG_Printf( S_COLOR_RED "Error parsing animation file: %s", filename ); + //return qfalse; + return -1; + } + + //CG_Printf( S_COLOR_RED "Cached File: %s\n", cgs.animsList[cgs.numAnims].animFileRoute ); + //return ++cg_numAnims; + + i = cg_numAnims; + if ( cg_numAnims < MAX_CLIENTS ) { + cg_numAnims++; //offset for the next time :) + } + + return i; + //return qtrue; +} + +/* +====================== +CG_InitModelData +by TiM + +Initialize default values +in case the crazy modder +left out some of the keys. + +In most cases, the fields +will just be left blank. +No point in using extra +resources if they weren't +specified. +====================== +*/ + +static void CG_InitModelData( clientInfo_t *ci ) { + + ci->holsterModel = 0; + ci->hasRanks = qfalse; + + //initialize all model + skin data as 0, so it can be told if they don't get + //values assigned in the script parser, in which case we exit. + ci->headModel = 0; + ci->torsoModel = 0; + ci->legsModel = 0; + + ci->headSkin = 0; + ci->headSkinBlink = 0; //doesn't matter if left 0; won't end the parser + ci->headSkinFrown = 0; + ci->headSkinFrownBlink = 0; + ci->torsoSkin = 0; + ci->legsSkin = 0; + + //doesn't matter if left 0 + ci->headBlinkTime.minSeconds = 0; + ci->headBlinkTime.maxSeconds = 0; + + ci->nextTalkTime = 0; + ci->currentTalkSkin = 0; + + ci->headSkinTalk[0] = 0; + ci->headSkinTalk[1] = 0; + ci->headSkinTalk[2] = 0; + ci->headSkinTalk[3] = 0; + + //animation.cfg former inits + ci->footsteps = FOOTSTEP_NORMAL; + VectorClear( ci->headOffset ); + ci->gender = GENDER_MALE; + Q_strncpyz(ci->soundPath, ci->charName, sizeof(ci->soundPath)); + + //set animIndex to -1. if still -1 at the end, we return false, coz we gotz no anims + ci->animIndex = -1; + ci->animSndIndex = -1; + + memset( &ci->boltonTags, 0, sizeof(ci->boltonTags)); + + ci->isHazardModel = qfalse; +} + +/* +===================== +CG_ParseSkinSetDataFile +by TiM + +Parses a separate.skinset +file to get the skin data +for this model. +====================== +*/ + +static qboolean CG_ParseSkinSetDataFile( clientInfo_t *ci, const char *skinSetFrame, const char *charName, const char *skinName ) +{ + char* skinStar; + char skinSetName[MAX_QPATH]; + char skinSetRoute[MAX_QPATH]; + char* token; + char* textPtr; + char buffer[5000]; + int len; + fileHandle_t f; + int n, i; + + if ( ( skinStar = strstr( skinSetFrame, "*" ) ) == NULL ) + { + CG_Printf( S_COLOR_RED "ERROR: No '*' specified in model skin set!\n" ); + return qfalse; + } + else + { + //star is at front + if ( skinStar == skinSetFrame ) + { + skinStar++; + Com_sprintf( skinSetName, sizeof( skinSetName ), "%s%s", skinName, skinStar ); + } + //star is at end + else if ((int)(skinStar - skinSetFrame)+1 == (int)strlen(skinSetFrame) ) + { + Q_strncpyz( skinSetName, skinSetFrame, strlen( skinSetFrame ) ); + Q_strcat( skinSetName, sizeof( skinSetName ), skinName ); + } + else + { + CG_Printf( "ERROR: The '*' in %s must be on either the start or end, not the middle.\n", skinSetFrame ); + return qfalse; + } + } + + //CG_Printf( S_COLOR_RED "DEBUG: skinSetName = %s \n", skinSetName ); + + Com_sprintf( skinSetRoute, sizeof( skinSetRoute ), "models/players_rpgx/%s/%s.skinset", charName, skinSetName ); + + len = trap_FS_FOpenFile( skinSetRoute, &f, FS_READ ); + + if ( len <= 0 ) + { + CG_Printf( S_COLOR_RED "ERROR: Could not open file: %s\n", skinSetRoute ); + return qfalse; + } + + if ( len > sizeof( buffer) - 1 ) + { + CG_Printf( S_COLOR_RED "ERROR: Imported file is too big for buffer: %s. Len is %i\n", skinSetRoute, len ); + return qfalse; + } + + trap_FS_Read( buffer, len, f ); + + trap_FS_FCloseFile( f ); + + if ( !buffer[0] ) + { + CG_Printf( S_COLOR_RED "ERROR: Could not import data from %s\n", skinSetRoute ); + return qfalse; + } + + buffer[len] = '\0'; + + textPtr = buffer; + + token = COM_Parse( &textPtr ); + + if ( Q_stricmp( token, "{" ) ) + { + CG_Printf( S_COLOR_RED "ERROR: Skinset %s did not start with a '{'\n", skinSetRoute ); + return qfalse; + } + else + { + while ( 1 ) + { //while we don't hit the closing brace + + token = COM_Parse( &textPtr ); //parse + if ( !token[0] ) { //error check + break; + } + + //head skin when blinking + //must be before headskin, or the damn thing will think the two are the same :P + if ( !Q_stricmp( token, "headSkinBlink" ) ) { + if ( COM_ParseString( &textPtr, &token ) ) { + continue; + } + + if ( !cg_noBlinkingHeads.integer ) { + ci->headSkinBlink = trap_R_RegisterSkin( token ); + + //We'll alert them, but not cancel the loop + if ( !ci->headSkinBlink ) + Com_Printf( S_COLOR_RED "WARNING: Couldn't load headSkinBlink: %s\n", token); + } + + continue; + } + + //head blink time + else if ( !Q_stricmpn( token, "headBlinkTime", 13 ) ) { + //Done this way so we know we got two valid args b4 proceeding + if ( COM_ParseInt( &textPtr, &n ) ) { //first arg + SkipRestOfLine( &textPtr ); + continue; + } + + if ( COM_ParseInt( &textPtr, &i ) ) { //2nd arg + SkipRestOfLine( &textPtr ); + continue; + } + + //Bug: if the stupid n00b of a modder made + //the minimum time larger than the max time >.< + if ( n > i ) + { + Com_Printf( S_COLOR_RED "ERROR: Minimum blink time was larger than maximum blink time.\n" ); + continue; + } + + if ( !cg_noBlinkingHeads.integer ) { + ci->headBlinkTime.minSeconds = n; + ci->headBlinkTime.maxSeconds = i; + } + continue; + } + + else if ( !Q_stricmpn( token, "headSkinFrownBlink", 18 ) ) { + if ( COM_ParseString( &textPtr, &token ) ) { + continue; + } + + if ( !cg_noFrowningHeads.integer ) { + ci->headSkinFrownBlink = trap_R_RegisterSkin( token ); + + if ( !ci->headSkinFrownBlink ) + Com_Printf( S_COLOR_RED "WARNING: Was unable to load frown blink skin: %s\n", token ); + } + + continue; + } + + else if ( !Q_stricmpn( token, "headSkinFrown", 13 ) ) { + if ( COM_ParseString( &textPtr, &token ) ) { + continue; + } + + //Only cache if we want to + if ( !cg_noFrowningHeads.integer ) { + ci->headSkinFrown = trap_R_RegisterSkin( token ); + } + + if ( !cg_noFrowningHeads.integer && !ci->headSkinFrown ) { + Com_Printf( S_COLOR_RED "WARNING: Couldn't load frowning skin: %s\n", token ); + } + continue; + } + + else if ( !Q_stricmpn( token, "torsoSkin", 9 ) ) { + if (COM_ParseString( &textPtr, &token ) ) { + continue; + } + + ci->torsoSkin = trap_R_RegisterSkin( token ); + if (!ci->torsoSkin ) { + Com_Printf( S_COLOR_RED "ERROR: Couldn't load torsoSkin: %s\n", token); + } + continue; + } + + else if ( !Q_stricmpn( token, "legsSkin", 8 ) ) { + if (COM_ParseString( &textPtr, &token ) ) { + continue; + } + + ci->legsSkin = trap_R_RegisterSkin( token ); + if (!ci->legsSkin ) { + Com_Printf( S_COLOR_RED "ERROR: Couldn't load legsSkin: %s\n", token); + } + continue; + } + + else if ( !Q_stricmpn( token, "headSkinTalk", 12 ) && !cg_noTalkingHeads.integer ) { + + token = COM_Parse( &textPtr ); //parse + if ( !token[0] ) { //error check + break; + } + + // if we found no {, then scrub the whole thing + if ( Q_stricmpn( token, "{", 1 ) ) { + continue; + } + else { + i = 0; + while ( 1 ) { + token = COM_Parse( &textPtr ); //parse + if ( !token[0] ) { //error check + break; + } + + ci->headSkinTalk[i] = trap_R_RegisterSkin( token ); + if ( !ci->headSkinTalk[i] ) { + Com_Printf( S_COLOR_RED "ERROR: Unable to parse headSkinTalk file: %s\n", token ); + break; + } + i++; + + //Com_Printf("Registered Skin: %i\n", i); + + if ( !Q_stricmpn( token, "}", 1) ) { + break; + } + + if ( i >= MAX_TALK_SKINS ) { + break; + } + } + } + continue; + } + //head skin + else if ( !Q_stricmpn( token, "headSkin", 8 ) ) { + if ( COM_ParseString( &textPtr, &token ) ) { + continue; + } + + ci->headSkin = trap_R_RegisterSkin( token ); + if ( !ci->headSkin ) { + Com_Printf( S_COLOR_RED "ERROR: Couldn't load headSkin: %s\n", token ); + return qfalse; + } + continue; + } + + if ( !Q_stricmpn( token, "}", 1) ) { + break; + } + } + } + + return qtrue; +} + + +/* +====================== +CG_ParseModelDataFile +by TiM + +Reads in the .model file +needed to put together +a character model. +====================== +*/ + +static qboolean CG_ParseModelDataFile( clientInfo_t *ci, const char *charName, + const char *modelName, const char *skinName ) { + fileHandle_t file; + int file_len; + char charText[20000]; + char *textPtr, *prevValue; + char fileName[MAX_QPATH]; + //char animPath[MAX_QPATH]; + int i, n; + char *token; + char legsFileRoute[MAX_QPATH]; + char animSndFileRoute[MAX_QPATH]; + qboolean skinSetFound=qfalse; + //size_t strLen; + + //create the file route + Com_sprintf( fileName, sizeof(fileName), "models/players_rpgx/%s/%s.model", charName, modelName); + + //Okay... gotta get the hang of ANSI C text parsing >.< + //first... I guess load the file + file_len = trap_FS_FOpenFile( fileName, &file, FS_READ ); + //Error handle + //if length was 0, ie file not found or was empty + if (file_len <= 0 ) { + return qfalse; + } + //Another error... if text is WAY bigger than our available buffer O_O + if ( file_len >= sizeof( charText ) - 1 ) { + Com_Printf( S_COLOR_RED "Model Data File %s too long... WAY too long\n", fileName ); + return qfalse; + } + + //initialize the buffer + memset( charText, 0, sizeof( charText ) ); + + //read data into char array + //i guess we use a char array so we can actually specify size/width. + trap_FS_Read( charText, file_len, file ); + //I guess this is needed to mark the EOF. + charText[file_len] = 0; + //Free memory. Close Files + trap_FS_FCloseFile( file ); + + //default values if needed + CG_InitModelData( ci ); + + //Used to just clear any previous parse temp data + COM_BeginParseSession(); + + //transfer our data from a char array to a char ptr. + //needed for the parsing func methinks + textPtr = charText; + + token = COM_Parse( &textPtr ); //COM_Parse seems to work by splitting up each line of text by the spaces, + //and then removes that chunk from the original + //Okay, we should have the beginning variable first... which should be a '{' + + //from the looks of this, I think we have to do this after + //every parse call. O_O + if ( !token[0] ) { + Com_Printf( S_COLOR_RED "No data found in model data buffer!\n"); + return qfalse; + } + + if ( Q_stricmp(token, "{" ) ) { + Com_Printf(S_COLOR_RED "Missing { in %s\n", fileName); + return qfalse; + } + + while ( 1 ) { + prevValue = textPtr; //set a backup + token = COM_Parse( &textPtr ); + + if (!token[0] || !token ) { //we've hit the end of the file. w00t! exit! + break; + } + + //if we randomly find a brace in here (ie a sub-struct that may have no header) + //just skip it. :P + if ( !Q_stricmpn( token, "{", 1 ) ) { + SkipBracedSection ( &textPtr ); + } + + if ( !Q_stricmpn( token, "animsConfig", 11 ) ) { + if( COM_ParseString( &textPtr, &token ) ) { + continue; + } + + ci->animIndex = CG_ParseAnimationFile( token ); + + //no valid anim file found. Don't give up hope though. + //We have a backup resort at the end if need be. :) + if ( ci->animIndex == -1 ) { + Com_Printf( S_COLOR_RED "WARNING: Was unable to load file %s.\n", token ); + } + + continue; + } + + //anim sounds config file + else if ( !Q_stricmpn( token, "animSoundsConfig", 16 ) ) { + if ( COM_ParseString( &textPtr, &token ) ) { + continue; + } + + //check to see if we have a valid animlist we can sync these + //sounds to. if not, we'll put the file route asside, and + //try again at the end. + if ( ci->animIndex >= 0 ) { + ci->animSndIndex = CG_ParseAnimationSndFile( token, ci->animIndex ); + + if ( ci->animSndIndex == -1 ) { + Com_Printf( S_COLOR_RED "WARNING: Unable to load file: %s\n", token ); + } + } + else { + Q_strncpyz( animSndFileRoute, token, sizeof( animSndFileRoute ) ); + } + + continue; + } + + //character's legs model + else if ( !Q_stricmpn( token, "legsModel", 9 ) ) { + + if( COM_ParseString( &textPtr, &token ) ) { + continue; + } + + ci->legsModel = trap_R_RegisterModel( token ); + if (!ci->legsModel) { + Com_Printf( S_COLOR_RED "ERROR: Unable to load legs model: %s\n", token); + return qfalse; + } + + //if loaded no anims yet, copy the legs route to this variable, + //and we'll try again at the end of the function + //if ( ci->animIndex == -1 ) { + Q_strncpyz( legsFileRoute, token, sizeof( legsFileRoute ) ); + //} Actually. just copy it regardless. Just in case + + continue; + } + + //character's torso model + else if ( !Q_stricmpn( token, "torsoModel", 10 ) ) { + if( COM_ParseString( &textPtr, &token ) ) { + continue; + } + ci->torsoModel = trap_R_RegisterModel( token ); + //Com_Printf("Torsomodel passed as %s, %i\n", token, (int)ci->torsoModel); + + if (!ci->torsoModel) { + Com_Printf( S_COLOR_RED "ERROR: Unable to load torso model: %s\n", token); + return qfalse; + } + continue; + } + + //character's headmodel + else if ( !Q_stricmpn( token, "headModel", 9 ) ) { + + //return true = no extra text found on this line - bad! O_O! + if( COM_ParseString( &textPtr, &token ) ) { + continue; + } + + ci->headModel = trap_R_RegisterModel( token ); + if (!ci->headModel) { + Com_Printf( S_COLOR_RED "ERROR: Unable to load head model: %s\n", token); + return qfalse; + } + continue; + } + + //holster model (basically just a null md3 with 2 tags: one for phaser, other for tric) + else if ( !Q_stricmpn( token, "holsterModel", 12 ) ) { + + if( COM_ParseString( &textPtr, &token ) ) { + continue; + } + + ci->holsterModel = trap_R_RegisterModel( token ); + + //You'd hope like hell this will never happen. :P + if (!ci->holsterModel) { + Com_Printf( S_COLOR_RED "ERROR: Unable to load holster model: %s\n", token); + return qfalse; + } + continue; + } + + // Custom bolton models... oi O_o + else if ( !Q_stricmpn( token, "boltonModels", 12 ) ) { + //needed coz '{' could also be on next line + token = COM_Parse( &textPtr ); + if ( !token[0] ) { //if that was it + break; + } else { //else, if next character is '{' + if ( !Q_stricmpn( token, "{", 1 ) ) { + token = COM_Parse( &textPtr ); + if ( !token[0] ) { + break; + } + //loop till we hit the end of the brackets + i = 0; + + while ( Q_stricmp( token, "}" ) ) { + if ( !Q_stricmpn( token, "BOLTON_", 7 ) ) { + + ci->boltonTags[i].modelBase = GetIDForString( BoltonTable, token ); + + if( COM_ParseString( &textPtr, &token ) ) { + continue; + } + + if (!Q_stricmpn( token, "tag_", 4 ) ) { + Q_strncpyz(ci->boltonTags[i].tagName, token, sizeof (ci->boltonTags[i].tagName) ); + + if( COM_ParseString( &textPtr, &token ) ) { + continue; + } + ci->boltonTags[i].tagModel = trap_R_RegisterModel( token ); + + if (!ci->boltonTags[i].tagModel) { + Com_Printf( S_COLOR_RED "WARNING: Unable to load bolton model: %s\n", token); + } + + i++; + + if (i > MAX_BOLTONS -1) { + break; + } + } + } + + //Com_Printf("Index: %i, Name: %s, Handle: %i\n", ci->boltonTags[ci->numBoltOns].modelBase, ci->boltonTags[ci->numBoltOns].tagName, ci->boltonTags[ci->numBoltOns].tagModel ); + token = COM_Parse( &textPtr ); + if ( !token[0] ) { + break; + } + } + } + } + } + + //whether char is allowed to wear ranks + else if ( !Q_stricmpn( token, "hasRanks", 8 ) ) { + if (COM_ParseInt(&textPtr, &n ) ) { + continue; + } + if ( n > 0 ) + ci->hasRanks = qtrue; + else + ci->hasRanks = qfalse; + continue; + } + + //player footsteps. + //FIXME: Is it possible to make these things dynamic, so we can + //put in our own footstep sounds? + /*else if ( !Q_stricmp( token, "footsteps" ) ) { + token = COM_Parse( &textPtr ); + if ( !token ) { + break; + } + if ( !Q_stricmp( token, "default" ) || !Q_stricmp( token, "normal" ) ) { + ci->footsteps = FOOTSTEP_NORMAL; + } else if ( !Q_stricmp( token, "borg" ) ) { + ci->footsteps = FOOTSTEP_BORG; + } else if ( !Q_stricmp( token, "reaver" ) ) { + ci->footsteps = FOOTSTEP_REAVER; + } else if ( !Q_stricmp( token, "species" ) ) { + ci->footsteps = FOOTSTEP_SPECIES; + } else if ( !Q_stricmp( token, "warbot" ) ) { + ci->footsteps = FOOTSTEP_WARBOT; + } else if ( !Q_stricmp( token, "boot" ) ) { + ci->footsteps = FOOTSTEP_BOOT; + } else if ( !Q_stricmp( token, "flesh" ) ) { // Old Q3 defaults, for compatibility. -PJL + ci->footsteps = FOOTSTEP_SPECIES; + } else if ( !Q_stricmp( token, "mech" ) ) { // Ditto + ci->footsteps = FOOTSTEP_BORG; + } else if ( !Q_stricmp( token, "energy" ) ) { // Ditto + ci->footsteps = FOOTSTEP_BORG; + } else { + CG_Printf( "Bad footsteps parm in %s: %s\n", fileName, token ); + } + continue; + } */ + + //offset for player head in the scoreboard or whatever + else if ( !Q_stricmp( token, "headoffset" ) ) { + for ( i = 0 ; i < 3 ; i++ ) { + token = COM_Parse( &textPtr ); + if ( !token ) { + break; + } + ci->headOffset[i] = atof( token ); + } + continue; + } + + //what gender the character is + else if ( !Q_stricmpn( token, "sex", 3 ) ) { + if (COM_ParseString( &textPtr, &token ) ) { + continue; + } + if ( token[0] == 'f' || token[0] == 'F' ) { + ci->gender = GENDER_FEMALE; + } else if ( token[0] == 'n' || token[0] == 'N' ) { + ci->gender = GENDER_NEUTER; + } else { + ci->gender = GENDER_MALE; + } + continue; + } + + //file path to model sound files + else if ( !Q_stricmpn( token, "soundPath", 9 ) ) { + if (COM_ParseString( &textPtr, &token ) ){ + continue; + } + + Q_strncpyz( ci->soundPath, token, sizeof(ci->soundPath) ); + continue; + } + + //TiM - The skinset is defined + else if ( !Q_stricmpn( token, "skinSet", 7 ) ) { + if ( COM_ParseString( &textPtr, &token ) ) { + continue; + } + + if ( CG_ParseSkinSetDataFile( ci, token, charName, skinName ) ) + { + skinSetFound = qtrue; + } + + continue; + } + } + + //if any of the models or skins were left blank, then output false. Coz we need them. :P + if (!ci->headModel || !ci->torsoModel || !ci->legsModel ) { + Com_Printf( S_COLOR_RED "One or more necessary model files weren't loaded from %s\n", fileName ); + return qfalse; + } + + if ( !skinSetFound ) + { + if ( !CG_ParseSkinSetDataFile( ci, va("%s_*", modelName, skinName ), charName, skinName ) ) + { + CG_Printf( S_COLOR_RED "ERROR: Tried loading default skin set, however it failed.\n"); + } + } + + if (!ci->headSkin || !ci->torsoSkin || !ci->legsSkin ) { + + Com_Printf( S_COLOR_RED "One or more necessary skin files weren't loaded from %s\n", fileName ); + return qfalse; + } + + //if modder specified no animations file route, or they did, and it sucked (ie -1 ), + //Then try looking for one in the same directory as the lower.mdr file + + //k... the goal of this is to take a string like + //models/players_rpgx/crewman_male/lower.mdr + //and turn it into + //models/players_rpgx/crewman_male/animation.cfg + + if ( ci->animIndex == -1 && strlen( legsFileRoute ) > 0 ) { + //get length of file route + i = (int)strlen(legsFileRoute); + + while( 1 ) { + //if we looped all the way to the end.... ie BAD + if (i <= 0) { + //we obviously have no animation directory :( + Com_Printf(S_COLOR_RED "ERROR: Was unable to calculate location of animation.cfg for %s\n", fileName); + return qfalse; + } + + //if this is the first '/' we come across from going from the end to the start + if (legsFileRoute[i] == '/' ) { + //copy i bytes of data from token to animpath (effectively giving us the route, with no file) + Q_strncpyz(legsFileRoute, legsFileRoute, (i = i + 2 )); //+2 for the null char these things auto assign at the end... i think + break; //won't work without it anyway :P + } + i--; + } + + //add animation.cfg to the end of the string + Q_strcat(legsFileRoute, sizeof(legsFileRoute), "animation.cfg"); + + //Com_Printf( S_COLOR_RED "WARNING: Failed to load animation file specified in model config, attempting to load %s\n", legsFileRoute ); + + //parse it ^_^ + ci->animIndex = CG_ParseAnimationFile( legsFileRoute ); + + if ( ci->animIndex < 0 ) { + Com_Printf( "Tried loading anim data from location %s, however nothing was valid.\n", legsFileRoute ); + return qfalse; + } + } + else { + if ( !legsFileRoute[0] ) { + Com_Printf( S_COLOR_RED "Couldn't load/locate any player animation data for player: %s.\n", charName ); + return qfalse; + } + } + + //We'll check again if we can load a sound config file after everything else + if ( ci->animSndIndex == -1 && animSndFileRoute[0] ) + { + ci->animSndIndex = CG_ParseAnimationSndFile( animSndFileRoute, ci->animIndex ); + + if ( ci->animSndIndex == -1 ) { + Com_Printf( S_COLOR_RED "ERROR: Unable to load sound config file: %s.\n", animSndFileRoute ); + } + } + + ci->animsFlushed = qfalse; + + //TiM: Cheap hack - let us specifically check for hazard models + if ( !Q_stricmp( modelName, "hazard" ) ) + ci->isHazardModel = qtrue; + + //holy fudgenuggets. after all that checking, we actually made it to the end and have a valid freaking + //model! OWNED! + return qtrue; +} + +/* +============================================================================= + +CLIENT INFO + +============================================================================= +*/ + +/* +//This function has been rpg-x'ed®! (by J2J and fixed by RedTechie) +static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) { + char *text_p; + int len; + int i; + char *token; +// char aniname[255]; + float fps; + int skip; + char text[20000]; +// char text2[20000]; + fileHandle_t f; + animation_t *animations; + + //Com_Printf(S_COLOR_RED"MAX_ANIMATIONS = %i\n", MAX_ANIMATIONS); //not needed + + animations = ci->animations; + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( len <= 0 ) { + return qfalse; + } + if ( len >= sizeof( text ) - 1 ) { + CG_Printf( "File %s too long\n", filename ); + return qfalse; + } + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + skip = 0; // quite the compiler warning + + //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; //Before redtechie change 0 + animations[i].initialLerp = 100;//Before redtechie change 0 + } + + while(1) { + + token = COM_Parse( &text_p ); + if ( !token || !token[0] ) { + break; + } + + i = GetIDForString(animTable, token); + + if(i == -1) + { + Com_Printf(S_COLOR_RED"WARNING: Unknown token %s in %s\n", token, filename); + continue; + } + + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + animations[i].firstFrame = atoi( token ); + + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + animations[i].numFrames = atoi( token ); + + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + + animations[i].loopFrames = atoi( token ); + + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + fps = atof( token ); + + //Com_Printf(S_COLOR_RED"INFO: fps = %s - %f\n", token, fps); //debug only + + if ( fps == 0 ) + { + fps = 1;//Don't allow divide by zero error + } + if ( fps < 0 ) + {//backwards + animations[i].frameLerp = floor(1000.0f / fps); + } + else + { + animations[i].frameLerp = ceil(1000.0f / fps); + } + + animations[i].initialLerp = ceil(1000.0f / fabs(fps)); + + } + return qtrue; +} + + +*/ +/* +========================== +\CG_RegisterClientSkin +========================== +*/ +/* +========================== +CG_RegisterClientSkin +========================== +*/ +/*static qboolean CG_RegisterClientSkin( clientInfo_t *ci, const char *modelName, const char *skinName ) { + char filename[MAX_QPATH]; + + Com_sprintf( filename, sizeof( filename ), "models/players2/%s/lower_%s.skin", modelName, skinName ); + ci->legsSkin = trap_R_RegisterSkin( filename ); + + Com_sprintf( filename, sizeof( filename ), "models/players2/%s/upper_%s.skin", modelName, skinName ); + ci->torsoSkin = trap_R_RegisterSkin( filename ); + + Com_sprintf( filename, sizeof( filename ), "models/players2/%s/head_%s.skin", modelName, skinName ); + ci->headSkin = trap_R_RegisterSkin( filename ); + + //Com_sprintf( filename, sizeof( filename ), "models/players2/%s/groups.cfg", modelName); + //strcpy(ci->race, BG_RegisterRace( filename )); + + if ( !ci->legsSkin || !ci->torsoSkin || !ci->headSkin ) { + return qfalse; + } + + return qtrue; +}*/ + +/* +========================== +CG_RegisterClientModelname +========================== +*/ +static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *charName, const char *modelName, const char *skinName ) { + char filename[MAX_QPATH]; + + // load cmodels before models so filecache works + + /*Com_sprintf( filename, sizeof( filename ), "models/players2/%s/lower.mdr", modelName ); + ci->legsModel = trap_R_RegisterModel( filename ); + if ( !ci->legsModel ) { + Com_sprintf( filename, sizeof( filename ), "models/players2/%s/lower.md3", modelName ); + ci->legsModel = trap_R_RegisterModel( filename ); + if ( !ci->legsModel ) { + Com_Printf( S_COLOR_RED"Failed to load model file %s\n", filename ); + return qfalse; + } + } + Com_sprintf( filename, sizeof( filename ), "models/players2/%s/upper.mdr", modelName ); + ci->torsoModel = trap_R_RegisterModel( filename ); + if ( !ci->torsoModel ) { + Com_sprintf( filename, sizeof( filename ), "models/players2/%s/upper.md3", modelName ); + ci->torsoModel = trap_R_RegisterModel( filename ); + if ( !ci->torsoModel ) { + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; + } + } + + Com_sprintf( filename, sizeof( filename ), "models/players2/%s/head.md3", modelName ); + ci->headModel = trap_R_RegisterModel( filename ); + if ( !ci->headModel ) { + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; + } + + // if any skins failed to load, return failure + if ( !CG_RegisterClientSkin( ci, modelName, skinName ) ) { + Com_Printf( "Failed to load skin file: %s : %s\n", modelName, skinName ); + return qfalse; + } + + // load the animations + Com_sprintf( filename, sizeof( filename ), "models/players2/%s/animation.cfg", modelName ); + if ( !CG_ParseAnimationFile( filename, ci ) ) {\ + Com_Printf( "Failed to load animation file %s\n", filename ); + return qfalse; + }*/ + + if ( !CG_ParseModelDataFile( ci, charName, modelName, skinName) ) { + //Com_Printf( S_COLOR_RED "ERROR: Failed to parse .model file for character: %s/%s/%s\n", charName, modelName, skinName ); + return qfalse; + } + + /*Com_sprintf( filename, sizeof( filename ), "models/players2/%s/icon_%s.jpg", modelName, skinName ); + ci->modelIcon = trap_R_RegisterShaderNoMip( filename ); + if ( !ci->modelIcon ) { + Com_Printf( "Failed to load icon file: %s\n", filename ); + return qfalse; + }*/ + + Com_sprintf( filename, sizeof( filename ), "models/players_rpgx/%s/model_icon.jpg", charName ); + ci->modelIcon = trap_R_RegisterShaderNoMip( filename ); + if ( !ci->modelIcon ) { + Com_Printf( S_COLOR_RED "ERROR: Failed to load icon file: %s\n", filename ); + //return qfalse; + } + + return qtrue; +} + +/* +==================== +CG_ColorFromString +==================== +*/ +static void CG_ColorFromString( const char *v, vec3_t color ) { + int val; + + VectorClear( color ); + + val = atoi( v ); + + if ( val < 1 || val > 7 ) { + VectorSet( color, 1, 1, 1 ); + return; + } + + if ( val & 1 ) { + color[2] = 1.0f; + } + if ( val & 2 ) { + color[1] = 1.0f; + } + if ( val & 4 ) { + color[0] = 1.0f; + } +} + +/* +=================== +CG_LoadClientInfo + +Load it now, taking the disk hits. +This will usually be deferred to a safe time +=================== +*/ +static void CG_LoadClientInfo( clientInfo_t *ci , int clientNum) { + const char *dir, *fallback; + int i; + const char *s; + char temp_string[200]; + + //if ( !CG_RegisterClientModelname( ci, ci->modelName, ci->skinName ) ) { + + //Com_Printf("charName = %s, modelName = %s, skinName = %s\n", ci->charName, ci->modelName, ci->skinName); + if ( !CG_RegisterClientModelname( ci, ci->charName, ci->modelName, ci->skinName ) ) { + if ( cg_buildScript.integer ) { + CG_Error( "CG_RegisterClientModelname( %s/%s/%s ) failed", ci->charName, ci->modelName, ci->skinName ); + } + + Com_Printf( S_COLOR_RED "ERROR: Failed to parse .model file for character: %s/%s/%s\n", ci->charName, ci->modelName, ci->skinName ); + + if ( !CG_RegisterClientModelname( ci, ci->charName, DEFAULT_MODEL, ci->skinName ) ) + { + if ( !CG_RegisterClientModelname( ci, cg_defaultChar.string, DEFAULT_MODEL, ci->skinName ) ) + { + // fall back + if ( cgs.gametype >= GT_TEAM ) + { + // keep skin name + if ( !CG_RegisterClientModelname( ci, DEFAULT_CHAR, DEFAULT_MODEL, ci->skinName ) ) { + CG_Error( "DEFAULT_CHAR / model /skin ( %s/%s/%s ) failed to register", + DEFAULT_CHAR, DEFAULT_MODEL, ci->skinName ); + } + } + else + { + if ( !CG_RegisterClientModelname( ci, cg_defaultChar.string, DEFAULT_MODEL, DEFAULT_SKIN ) ) + { + if ( !CG_RegisterClientModelname( ci, DEFAULT_CHAR, DEFAULT_MODEL, DEFAULT_SKIN ) ) + { + CG_Error( "DEFAULT_CHAR (%s) failed to register", DEFAULT_CHAR ); + } + } + } + } + } + } + + // sounds + dir = ci->soundPath; + fallback = (ci->gender==GENDER_FEMALE)?"hm_female":"hm_male"; + + for ( i = 0 ; i < MAX_CUSTOM_SOUNDS ; i++ ) { + s = cg_customSoundNames[i]; + if ( !s ) { + break; + } + ci->sounds[i] = trap_S_RegisterSound( va("sound/voice/%s/misc/%s", dir, s + 1) ); + if ( !ci->sounds[i] ) { + ci->sounds[i] = trap_S_RegisterSound( va("sound/player/%s/%s", fallback, s + 1) ); + } + } + + ci->deferred = qfalse; + + Com_sprintf(temp_string, sizeof(temp_string), "%s/%s/%s", ci->charName, ci->modelName, ci->skinName); + updateSkin(clientNum, temp_string); + + // reset any existing players and bodies, because they might be in bad + // frames for this new model + for ( i = 0 ; i < MAX_GENTITIES ; i++ ) { + if ( cg_entities[i].currentState.clientNum == clientNum + && cg_entities[i].currentState.eType == ET_PLAYER ) + { + CG_ResetPlayerEntity( &cg_entities[i] ); + } + } +} + + +// we need to check here to see if the clientinfo model variable is the same as the one that is in the +// clientinfo block. This is because it is possible for the server to change skins on us when we hit a CTF +// teamplay game where groups are defined. +// most of the time this will not hit + +void updateSkin(int clientNum, char *new_model) +{ + char model_string[200]; + + // create string to be checked against + trap_Cvar_VariableStringBuffer("model", model_string, sizeof(model_string) ); + + if (Q_stricmp(new_model, model_string) && cg.validPPS && (clientNum == cg.predictedPlayerState.clientNum)) + { + trap_Cvar_Set_No_Modify ("model",new_model); + } +} + + +/* +====================== +CG_CopyClientInfoModel +====================== +*/ +static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to ) { + //int i; + + VectorCopy( from->headOffset, to->headOffset ); + to->footsteps = from->footsteps; + to->gender = from->gender; + to->numTaunts = from->numTaunts; + + to->legsModel = from->legsModel; + to->legsSkin = from->legsSkin; + to->torsoModel = from->torsoModel; + to->torsoSkin = from->torsoSkin; + to->headModel = from->headModel; + to->headSkin = from->headSkin; + to->modelIcon = from->modelIcon; + + to->animIndex = from->animIndex; + to->animSndIndex = from->animSndIndex; + + to->hasRanks = from->hasRanks; + + //Blinking + if ( from->headSkinBlink ) { + to->headSkinBlink = from->headSkinBlink; + + if ( from->headBlinkTime.maxSeconds > 0 ) { + //memcpy( to->headBlinkTime, from->headBlinkTime, sizeof( to->headBlinkTime ) ); + to->headBlinkTime = from->headBlinkTime; + } + } + + //Frowning/Blink Frowning + if ( from->headSkinFrown ) { + to->headSkinFrown = from->headSkinFrown; + + if ( from->headSkinFrownBlink ) + to->headSkinFrownBlink = from->headSkinFrownBlink; + } + + //Copy over bolton info + /*if ( from->boltonTags[0].tagModel && from->boltonTags[0].tagName ) { //if there actually is bolton data... + for (i = 0; i < MAX_BOLTONS; i++ ) { //loop thru all of them + if ( from->boltonTags[i].tagModel && from->boltonTags[i].tagName ) { //only work on ones that actually have data. + /*to->boltonTags[i].modelBase = from->boltonTags[i].modelBase; + to->boltonTags[i].tagModel = from->boltonTags[i].tagModel; + Q_strncpyz( to->boltonTags[i].tagName, from->boltonTags[i].tagName, sizeof (to->boltonTags[i].tagName) );*/ + memcpy( to->boltonTags, from->boltonTags, sizeof(to->boltonTags) ); +// } +// } +// } + + //Talking skin data + /*if ( from->headSkinTalk[0] ) { + for (i = 0; i < MAX_TALK_SKINS; i++ ) { + if ( from->headSkinTalk[i] ) {*/ + memcpy( to->headSkinTalk, from->headSkinTalk, sizeof( to->headSkinTalk ) ); + /* } + } + }*/ + + to->holsterModel = from->holsterModel; + + Q_strncpyz( to->soundPath, from->soundPath, sizeof (to->soundPath) ); + //memcpy( to->animations, from->animations, sizeof( to->animations ) ); + //TiM : New animation method :) + to->animIndex = from->animIndex; + memcpy( to->sounds, from->sounds, sizeof( to->sounds ) ); + + to->isHazardModel = from->isHazardModel; +} + +/* +====================== +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; + } + if ( !Q_stricmp(ci->charName, match->charName) && !Q_stricmp( ci->modelName, match->modelName ) + && !Q_stricmp( ci->skinName, match->skinName ) ) { + // 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 clientNum ) { + int i; + clientInfo_t *match; + + // if we are in teamplay, only grab a model if the skin is correct + if ( cgs.gametype >= GT_TEAM ) { + // this is ONLY for optimization - it's exactly the same effect as CG_LoadClientInfo + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid ) { + continue; + } + if ( Q_stricmp( ci->skinName, match->skinName ) ) { + continue; + } + ci->deferred = qtrue; + CG_CopyClientInfoModel( match, ci ); + return; + } + + // load the full model, because we don't ever want to show + // an improper team skin. This will cause a hitch for the first + // player, when the second enters. Combat shouldn't be going on + // yet, so it shouldn't matter + CG_LoadClientInfo( ci, clientNum ); + return; + } + + // find the first valid clientinfo and grab its stuff + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid ) { + continue; + } + + ci->deferred = qtrue; + CG_CopyClientInfoModel( match, ci ); + return; + } + + // we should never get here... + CG_Printf( "CG_SetDeferredClientInfo: no valid clients!\n" ); + + CG_LoadClientInfo( ci ,clientNum); +} + + +/* +====================== +CG_NewClientInfo +====================== +*/ +void CG_NewClientInfo( int clientNum ) { + clientInfo_t *ci; + clientInfo_t newInfo; + const char *configstring; + const char *v; + char *model; + char *skin; + size_t len; + //int i; + + ci = &cgs.clientinfo[clientNum]; + + configstring = CG_ConfigString( clientNum + CS_PLAYERS ); + if ( !configstring[0] ) { + memset( ci, 0, sizeof( *ci ) ); + return; // player just left + } + + // build into a temp buffer so the defer checks can use + // the old value + memset( &newInfo, 0, sizeof( newInfo ) ); + + // isolate the player's name + v = Info_ValueForKey(configstring, "n"); + Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) ); + + // colors + //v = Info_ValueForKey( configstring, "c1" ); + //CG_ColorFromString( v, newInfo.color ); + + // bot skill + v = Info_ValueForKey( configstring, "skill" ); + newInfo.botSkill = atoi( v ); + + // handicap + v = Info_ValueForKey( configstring, "hc" ); + newInfo.handicap = atoi( v ); + + // wins + v = Info_ValueForKey( configstring, "w" ); + newInfo.wins = atoi( v ); + + // losses + v = Info_ValueForKey( configstring, "l" ); + newInfo.losses = atoi( v ); + + // team + v = Info_ValueForKey( configstring, "t" ); + newInfo.team = atoi( v ); + + // playerclass + v = Info_ValueForKey( configstring, "p" ); + newInfo.pClass = atoi( v ); + + // model + v = Info_ValueForKey( configstring, "model" ); + if ( cg_forceModel.integer ) { + // forcemodel makes everyone use a single model + // to prevent load hitches + char charStr[MAX_QPATH]; + char *model; + char *skin; + size_t len; + + trap_Cvar_VariableStringBuffer( "model", charStr, sizeof( charStr ) ); + if ( ( model = strchr( charStr, '/' ) ) == NULL) { + model = "main"; + skin = "default"; + } else { + *model = 0; //*model++ = 0; + len = strlen(model); + + //if there was a slash, but no model afterwards + if ( !model || !model[1] ) { + model = "main"; + } + + if ( ( skin = strchr( model, '/' ) ) == NULL ) { + skin = "default"; + } else { + *skin = 0; //*skin++ = 0; + + if ( !skin || !skin[1] ) { + skin = "default"; + } + + Com_sprintf( model, len - strlen(skin), model); + } + } + + Q_strncpyz( newInfo.skinName, skin, sizeof( newInfo.skinName ) ); + Q_strncpyz( newInfo.modelName, model, sizeof( newInfo.modelName ) ); + Q_strncpyz( newInfo.charName, charStr, sizeof( newInfo.charName ) ); + +// Q_strncpyz( newInfo.modelName, DEFAULT_MODEL, sizeof( newInfo.modelName ) ); +// Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); + + if ( cgs.gametype >= GT_TEAM ) { + // keep skin name + skin = strchr( v, '/' ); + if ( model ) { + Q_strncpyz( newInfo.skinName, skin + 1, sizeof( newInfo.skinName ) ); + } + } + } else { + //Q_strncpyz( newInfo.modelName, v, sizeof( newInfo.modelName ) ); + //Okay! Here we go! We gotta take a string like kulhane/admiral/teal + //divide it, and put it into three different vars, accounting for user n00biness + //(ie mistakes and stuff) along the way. + + //step 1, take the first bit of the string and put it in the charName var. + if ( ( model = strchr( v, '/') ) == NULL ) { //if there's no slash + Q_strncpyz( newInfo.charName, v, sizeof( newInfo.charName ) ); //just set it + } else { //otherwise, isolate the first bit, and copy that + len = strlen( v ); + Q_strncpyz( newInfo.charName, v, ((int)len - (int)strlen(model)) + 1 ); + } + + //Com_Printf("%s\n", newInfo.charName); + + //slash = strchr( newInfo.modelName, '/' ); + if ( !model || !model[1] ) { + // modelName didn not include a skin name + //Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); + Q_strncpyz( newInfo.modelName, "main", sizeof( newInfo.modelName ) ); + Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); + + if ( model && !model[1] ) + {//if we had a slash, but nothing after, clear it + *model = 0; + } + } else { + model++; //bypass the slash //QVMNOTE + len = strlen(model); + skin = strchr( model, '/' ); + + //if there was a model defined, but no skin + if ( !skin || !skin[1] ) { + //no skin, but I'm guessing we gotz a model at least + if ( !skin ) { + Q_strncpyz( newInfo.modelName, model, sizeof( newInfo.modelName ) ); + } + else { + if ( !skin[1] ) { + Q_strncpyz( newInfo.modelName, model, (int)strlen(model) ); + } + } + + Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); + + if ( skin && !skin[1] ) { + *skin = 0; + } + } else { + skin++; //QVMNOTE + Q_strncpyz( newInfo.modelName, model, ((int)len - (int)strlen(skin)) ); + Q_strncpyz( newInfo.skinName, skin, sizeof( newInfo.skinName ) ); + } + + //Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); + // truncate modelName + *model = 0; + } + + } + + //TiM: PMS - age + v = Info_ValueForKey( configstring, "age" ); + Q_strncpyz( newInfo.age, v, sizeof(newInfo.age) ); + + //PMS - height + v = Info_ValueForKey( configstring, "height" ); + newInfo.height = atof( v ); + + //PMS - weight + v = Info_ValueForKey( configstring, "weight" ); + newInfo.weight = atof( v ); + + //PMS - race + v = Info_ValueForKey( configstring, "race" ); + Q_strncpyz( newInfo.race, v, sizeof(newInfo.race) ); + + //TiM : Offset for the emote system and solid chairs + v = Info_ValueForKey( configstring, "of" ); + newInfo.modelOffset = atoi( v ); + //CG_Printf( "Set modeloffset as: %f\n", newInfo.modelOffset ); + + v = Info_ValueForKey( configstring, "admin" ); + newInfo.isAdmin = atoi( v ); + + //ensure the health value is carried over + //it normally only gets updated when it itself is changed + //newInfo.health = ci->health; + //Actually... this might actually screw it up on server start time + + // 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() < 2000000; + + // if we are defering loads, just have it pick the first valid + if ( forceDefer || + ( cg_deferPlayers.integer && !cg_buildScript.integer && !cg.loading && + ((clientNum != cg.predictedPlayerState.clientNum) && cg.validPPS) ) ) { + // keep whatever they had if it won't violate team skins + if ( ci->infoValid && + ( cgs.gametype < GT_TEAM || !Q_stricmp( newInfo.skinName, ci->skinName ) ) ) { + CG_CopyClientInfoModel( ci, &newInfo ); + newInfo.deferred = qtrue; + } else { + // use whatever is available + CG_SetDeferredClientInfo( &newInfo, clientNum ); + } + // if we are low on memory, leave them with this model + if ( forceDefer ) { + CG_Printf( "Memory is low. Using deferred model.\n" ); + newInfo.deferred = qfalse; + } + } else { + CG_LoadClientInfo( &newInfo, clientNum ); + } + } + + // replace whatever was there with the new one + newInfo.infoValid = qtrue; + *ci = newInfo; +} + + +/* +====================== +CG_LoadDeferredPlayers + +Called each frame when a player is dead +and the scoreboard is up +so deferred players can be loaded +====================== +*/ +void CG_LoadDeferredPlayers( void ) { + int i; + clientInfo_t *ci; + + // scan for a deferred player to load + for ( i = 0, ci = cgs.clientinfo ; i < cgs.maxclients ; i++, ci++ ) { + if ( ci->infoValid && ci->deferred ) { + // if we are low on memory, leave it deferred + if ( trap_MemoryRemaining() < 4000000 ) { + CG_Printf( "Memory is low. Using deferred model.\n" ); + ci->deferred = qfalse; + continue; + } + CG_LoadClientInfo( ci, i ); +// break; + } + } +} + +/* +====================== +CG_NewDecoyInfo + +TiM: A decoy was spawned, +so the relevant data will +be set up so it may be displayed +independantly of its spawner player. +====================== +*/ +void CG_NewDecoyInfo( int decoyNum ) { + clientInfo_t *ci; + char *userinfo; + int i; + char *v; + char *temp, *temp2; + //char charName[MAX_QPATH], modelName[MAX_QPATH], skinName[MAX_QPATH]; + int len; + qboolean noMemoryLeft=qfalse; + + ci = &cgs.decoyInfo[decoyNum]; + noMemoryLeft = ( trap_MemoryRemaining() < 4000000 ); + + //First, check if force player models is on. if so, copy all the data from us to the decoy. + //Or, if we're low on memory, let's do this anyway + if ( cg_forceModel.integer || noMemoryLeft ) { + *ci = cgs.clientinfo[cg.predictedPlayerState.clientNum]; + + if ( noMemoryLeft ) + CG_Printf( S_COLOR_RED "WARNING: Very little memory remains. Decoy data is being deferred to player's active data.\n" ); + + ci->infoValid = qtrue; + return; + } + + //CG_Printf( S_COLOR_RED "decoy ID: %i\n", decoyNum ); + + //Get the necessary decoy data + userinfo = (char *)CG_ConfigString( CS_DECOYS + decoyNum ); + + if ( !userinfo || !userinfo[0] ) { //No data, so flush and continue + memset( ci, 0, sizeof( clientInfo_t ) ); + return; + } + + //CG_Printf( S_COLOR_RED "%s\n", userinfo ); + + //Get model string + v = Info_ValueForKey( userinfo, "model" ); + + //First thing's first. We need to isolate these into three strings: model, char, skin + { + //step 1, take the first bit of the string and put it in the charName var. + if ( ( temp = strchr( v, '/') ) == NULL ) { //if there's no slash + Q_strncpyz( ci->charName, v, sizeof( ci->charName ) ); //just set it + } else { //otherwise, isolate the first bit, and copy that + len = strlen( v ); + Q_strncpyz( ci->charName, v, ((int)len - (int)strlen(temp)) + 1 ); + } + + //Com_Printf("%s\n", newInfo.charName); + + //slash = strchr( newInfo.modelName, '/' ); + if ( !temp || !temp[1] ) { + // modelName did not include a skin name + //Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); + Q_strncpyz( ci->modelName, "main", sizeof( ci->modelName ) ); + Q_strncpyz( ci->skinName, "default", sizeof( ci->skinName ) ); + + if ( temp && !temp[1] ) + {//if we had a slash, but nothing after, clear it + *temp = 0; + } + } else { + temp++; //bypass the slash + len = strlen(temp); + temp2 = strchr( temp, '/' ); + + //if there was a model defined, but no skin + if ( !temp2 || !temp2[1] ) { + //no skin, but I'm guessing we gotz a model at least + if ( !temp2 ) { + Q_strncpyz( ci->modelName, temp, sizeof( ci->modelName ) ); + } + else { + if ( !temp2[1] ) { + Q_strncpyz( ci->modelName, temp, (int)strlen(temp) ); + } + } + + Q_strncpyz( ci->skinName, "default", sizeof( ci->skinName ) ); + + if ( temp2 && !temp2[1] ) { + *temp2 = 0; + } + } else { + temp2++; + Q_strncpyz( ci->modelName, temp, ((int)len - (int)strlen(temp2)) ); + Q_strncpyz( ci->skinName, temp2, sizeof( ci->skinName ) ); + } + } + } + + //k... get the additional parms from the config string n' put em here + v = Info_ValueForKey( userinfo, "weight" ); + ci->weight = atof( v ); + v = Info_ValueForKey( userinfo, "height" ); + ci->height = atof( v ); + v = Info_ValueForKey( userinfo, "moOf" ); + ci->modelOffset = atoi( v ); + v = Info_ValueForKey( userinfo, "c" ); + ci->pClass = atoi( v ); + + ci->animsFlushed = qtrue; + + //Okay... if another player actively has the skin we want, let's pilfer that rather than load it like a schmuck rofl. + { + clientInfo_t *match; + + for ( i = 0; i < cgs.maxclients; i++ ) { + match = &cgs.clientinfo[i]; + + //We found a match! ^_^ + if ( !Q_stricmp( match->charName, ci->charName ) && + !Q_stricmp( match->modelName, ci->modelName ) && + !Q_stricmp( match->skinName, ci->skinName ) ) + { + CG_CopyClientInfoModel( match, ci ); + ci->infoValid = qtrue; + return; + } + } + } + + + //*sigh... guess worse came to worse... we gotta fricken load it. :'( + if ( !CG_ParseModelDataFile( ci, ci->charName, ci->modelName, ci->skinName ) ) + { + CG_Printf( S_COLOR_RED "ERROR: Unable to load character for decoy: %s/%s/%s\n", ci->charName, ci->modelName, ci->skinName ); + + if ( !CG_ParseModelDataFile( ci, ci->charName, DEFAULT_MODEL, DEFAULT_SKIN ) ) + { + if (!CG_ParseModelDataFile( ci, cg_defaultChar.string, ci->modelName, ci->skinName ) ) + { + if ( !CG_ParseModelDataFile( ci, cg_defaultChar.string, DEFAULT_MODEL, DEFAULT_SKIN ) ) + { + //if we hit this.... oh so bad... O_o + if ( !CG_ParseModelDataFile( ci, DEFAULT_CHAR, DEFAULT_MODEL, DEFAULT_SKIN ) ) + CG_Error( "DEFAULT_CHAR / model / skin ( %s/%s/%s ) failed to register", DEFAULT_CHAR, DEFAULT_MODEL, DEFAULT_SKIN ); + } + } + } + } + + ci->infoValid = qtrue; +} + +/* +============================================================================= + +PLAYER ANIMATION + +============================================================================= +*/ + + +/* +=============== +CG_SetLerpFrameAnimation + +may include ANIM_TOGGLEBIT +=============== +*/ +static void CG_SetLerpFrameAnimation( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { + animation_t *anim; + + lf->animationNumber = newAnimation; + newAnimation &= ~ANIM_TOGGLEBIT; + + if ( newAnimation < 0 || newAnimation >= MAX_ANIMATIONS ) { + CG_Error( "Bad animation number: %i", newAnimation ); + } + + //CG_Printf("animIndex: %i\n", ci->animIndex ); + //anim = &ci->animations[ newAnimation ]; + anim = &cg_animsList[ ci->animIndex ].animations[ newAnimation ]; + //CG_Printf(S_COLOR_RED "%i\n", ci->animIndex ); + + lf->animation = anim; + lf->animationTime = lf->frameTime + anim->initialLerp; + + if ( cg_debugAnim.integer ) { + CG_Printf( "Anim: %s (%i)\n", GetStringForID(animTable, newAnimation), newAnimation ); + //CG_Printf( "Anim: %i\n", newAnimation ); + } +} + +/* +=============== +CG_RunLerpFrame + +Sets cg.snap, cg.oldFrame, and cg.backlerp +cg.time should be between oldFrameTime and frameTime after exit +=============== +*/ +static qboolean CG_RunLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float speedScale ) { + int f; + animation_t *anim; + qboolean newFrame = qfalse; + float frameLerp; + + // debugging tool to get no animations + if ( cg_animSpeed.integer == 0 ) { + lf->oldFrame = lf->frame = lf->backlerp = 0; + return qfalse; + } + + // see if the animation sequence is switching + if ( newAnimation != lf->animationNumber || !lf->animation ) { + CG_SetLerpFrameAnimation( ci, lf, newAnimation ); + } + + // if we have passed the current frame, move it to + // oldFrame and calculate a new frame + if ( cg.time >= lf->frameTime ) { + lf->oldFrame = lf->frame; + lf->oldFrameTime = lf->frameTime; + + // get the next frame based on the animation + anim = lf->animation; + if ( !anim->frameLerp ) { + return qfalse; // shouldn't happen + } + //RPG-X Check. Anims with lengths < 0 are emote stubs. + //If we get one, hardcode to override to default standing. + //Otherwise, we'll get complicated glitches. + if ( anim->numFrames < 0 ) { + CG_SetLerpFrameAnimation( ci, lf, BOTH_STAND1 ); + } + + //TiM - Calc frame lerp scale here, else the frames + //just snap to each other + frameLerp = (float)anim->frameLerp + (anim->frameLerp*(1.0f - speedScale)); + if ( frameLerp < 1.0f ) + frameLerp = 1.0f; + + //CG_Printf( "Lerp: %f\n", frameLerp ); + + if ( cg.time < lf->animationTime ) { + lf->frameTime = lf->animationTime; // initial lerp + } else { + lf->frameTime = lf->oldFrameTime + frameLerp;//anim->frameLerp; + } + f = ( lf->frameTime - lf->animationTime ) / frameLerp;//anim->frameLerp; + //f *= speedScale; // adjust for haste, etc + if ( f >= anim->numFrames ) { + f -= anim->numFrames; + if ( anim->loopFrames ) { + f %= anim->loopFrames; + f += anim->numFrames - anim->loopFrames; + } else { + f = anim->numFrames - 1; + // the animation is stuck at the end, so it + // can immediately transition to another sequence + lf->frameTime = cg.time; + } + } + lf->frame = anim->firstFrame + f; + if ( cg.time > lf->frameTime ) { + lf->frameTime = cg.time; + if ( cg_debugAnim.integer ) { + CG_Printf( "Clamp lf->frameTime\n"); + } + } + newFrame = qtrue; + } + + if ( lf->frameTime > cg.time + 200 ) { + lf->frameTime = cg.time; + } + + if ( lf->oldFrameTime > cg.time ) { + lf->oldFrameTime = cg.time; + } + // calculate current lerp value + if ( lf->frameTime == lf->oldFrameTime ) { + lf->backlerp = 0; + } else { + lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); + } + + return newFrame; +} + + +/* +=============== +CG_ClearLerpFrame +=============== +*/ +//This function has been rpg-x'ed®! (by RedTechie) +void CG_ClearLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int animationNumber ) { //RPG-X: RedTechie - Changed type from static void to void + + lf->frameTime = lf->oldFrameTime = cg.time; + lf->animation = 0; + CG_SetLerpFrameAnimation( ci, lf, animationNumber ); + lf->oldFrame = lf->frame = lf->animation->firstFrame; + + //RPG-X: RedTechie - Added this block of code + /*if ( lf->animation->frameLerp < 0 ) + {//Plays backwards + lf->oldFrame = lf->frame = (lf->animation->firstFrame + lf->animation->numFrames); + } + else + { + lf->oldFrame = lf->frame = lf->animation->firstFrame; + }*/ + + //TiM - Put in, for now + +} + + +/* +=============== +CG_PlayerAnimation +=============== +*/ +extern qboolean PM_PlayerWalking( int anim ); +extern qboolean PM_PlayerRunning( int anim ); +extern qboolean PM_PlayerCrouchWalking( int anim ); + +static void CG_PlayerAnimation( centity_t *cent, int *legsOld, int *legs, float *legsBackLerp, + int *torsoOld, int *torso, float *torsoBackLerp ) { + clientInfo_t *ci; + int clientNum; + float speedScale=1; + qboolean newLegsFrame = qfalse; + qboolean newTorsoFrame = qfalse; + //float speed; + qboolean isDecoy = cent->currentState.eFlags & EF_ITEMPLACEHOLDER; + + clientNum = cent->currentState.clientNum; + + if ( cg_noPlayerAnims.integer ) { + *legsOld = *legs = *torsoOld = *torso = 0; + return; + } + + //if ( cent->currentState.powerups & ( 1 << PW_HASTE ) ) { + // speedScale = 1.5; + //} else { + // speedScale = 1; + //} + //CG_Printf( "Vel: %f\n", VectorLength(cent->currentState.pos.trDelta)); + + //TiM + // 250 = default running speed + // 125 = default walking speed + // 90 = default crouchwalk speed + //if ( !isDecoy ) + //{ + // if ( clientNum == cg.snap->ps.clientNum ) + // speed = VectorLength( cg.predictedPlayerState.velocity ); + // else + // speed = VectorLength(cent->currentState.pos.trDelta); + // //if ( speed < 50.0f ) + // //speed = 50.0f; + + // if ( PM_PlayerWalking( cent->currentState.legsAnim ) ) + // { + // speedScale = speed / 125.0f; + // } + // else if ( PM_PlayerRunning( cent->currentState.legsAnim ) ) + // { + // speedScale = speed / 250.0f; + // } + // else if ( PM_PlayerCrouchWalking( cent->currentState.legsAnim ) ) + // { + // speedScale = speed / 90.0f; + // } + // else + // { + // speedScale = 1.0f; + // } + //} + + + if ( isDecoy ) + ci = &cgs.decoyInfo[ cent->currentState.eventParm ]; + else + ci = &cgs.clientinfo[ clientNum ]; + + // do the shuffle turn frames locally + if ( !cent->clampAngles && cent->pe.legs.yawing && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == BOTH_STAND1 ) { //TORSO_STAND + newLegsFrame = CG_RunLerpFrame( ci, ¢->pe.legs, LEGS_TURN1, speedScale ); + } else { + newLegsFrame = CG_RunLerpFrame( ci, ¢->pe.legs, cent->currentState.legsAnim, speedScale ); + } + + if( newLegsFrame && ci->animSndIndex >= 0 && !(cent->currentState.powerups & ( 1 << PW_INVIS ) ) ) + { + trace_t tr; + vec3_t endPoint; + //qboolean metal = qfalse; //Uberhack meant specifically for metal clank surfaces + vec3_t mins = { -16, -16, 0 }; + vec3_t maxs = { 16, 16, 0 }; + int surfType; + + //TiM: Lower based sounds are always to do with things like shoes clopping n' stuff. + //This portion of code makes sure the player is on a solid surface in order to play this sound + VectorCopy( cent->lerpOrigin, endPoint); + endPoint[2] -= 24.50f; + CG_Trace( &tr, cent->lerpOrigin, mins, maxs, endPoint, cent->currentState.clientNum, MASK_PLAYERSOLID | CONTENTS_LADDER ); + //trap_CM_BoxTrace( &tr, cent->lerpOrigin, endPoint, mins, maxs, 0, MASK_PLAYERSOLID ); + //metal = tr.surfaceFlags & SURF_METALSTEPS || (tr.contents & CONTENTS_LADDER); + if(tr.surfaceFlags & SURF_METALSTEPS || (tr.contents & CONTENTS_LADDER)) + surfType = 1; + else if(tr.surfaceFlags & SURF_GRASS) + surfType = 2; + else if(tr.surfaceFlags & SURF_GRAVEL) + surfType = 3; + else if(tr.surfaceFlags & SURF_SNOW) + surfType = 4; + else if(tr.surfaceFlags & SURF_WOOD) + surfType = 5; + else + surfType = 0; + + //if there's something below us, or we're free floating in something like water/lava/slime + if ( tr.fraction != 1.0f || (tr.contents & MASK_WATER ) || (tr.contents & CONTENTS_LADDER) ) { + CG_PlayerAnimSounds( cg_animsSndList[ci->animSndIndex].lowerAnimSounds, cent->pe.legs.frame, cent->currentState.clientNum, /*metal*/surfType ); + } + } + + *legsOld = cent->pe.legs.oldFrame; + *legs = cent->pe.legs.frame; + *legsBackLerp = cent->pe.legs.backlerp; + + //if ( PM_PlayerWalking( cent->currentState.torsoAnim ) ) + //{ + // speedScale *= speed / 125.0f; + //} + //else if ( PM_PlayerRunning( cent->currentState.torsoAnim ) ) + //{ + // speedScale *= speed / 250.0f; + //} + //else if ( PM_PlayerCrouchWalking( cent->currentState.torsoAnim ) ) + //{ + // speedScale *= speed / 90.0f; + //} + //else + //{ + // speedScale = 1.0f; + //} + + newTorsoFrame = CG_RunLerpFrame( ci, ¢->pe.torso, cent->currentState.torsoAnim, speedScale ); + + if( newTorsoFrame && ci->animSndIndex >= 0 ) + { + CG_PlayerAnimSounds( cg_animsSndList[ci->animSndIndex].upperAnimSounds, cent->pe.torso.frame, cent->currentState.clientNum, /*qfalse*/0 ); + } + + *torsoOld = cent->pe.torso.oldFrame; + *torso = cent->pe.torso.frame; + *torsoBackLerp = cent->pe.torso.backlerp; +} + +/* +============================================================================= + +PLAYER ANGLES + +============================================================================= +*/ + +/* +================== +CG_SwingAngles +================== +*/ +static void CG_SwingAngles( float destination, float swingTolerance, float clampTolerance, + float speed, float *angle, qboolean *swinging, qboolean pitch ) { + float swing; + float move; + float scale; + + swing = AngleSubtract( destination, *angle ); + + /*if ( canSwing ) { + if ( swing == 0 ) { + *swinging = qfalse; + } else if ( swing >= clampTolerance || swing <= -clampTolerance ) { + *swinging = qtrue; + } /*else { + *swinging = qtrue; + } + } + else {*/ + if ( !*swinging ) { + // see if a swing should be started + //swing = AngleSubtract( *angle, destination ); + + if ( swing == 0 ) { + *swinging = qfalse; + } + + else if ( !pitch && ( swing >= clampTolerance || swing <= -clampTolerance ) ) { + *swinging = qtrue; + } + + if ( pitch ) + *swinging = qtrue; + + /*else if ( swing > swingTolerance || swing < -swingTolerance ) { + *swinging = qtrue; + }*/ + } + //} + + if ( !*swinging ) { + return; + } + + // modify the speed depending on the delta + // so it doesn't seem so linear + swing = AngleSubtract( destination, *angle ); + scale = fabs( swing ); + if ( scale < swingTolerance * 0.5 ) { + scale = 0.5; + } else if ( scale < swingTolerance ) { + scale = 1.0; + } else { + scale = 2.0; + } + + // swing towards the destination angle + if ( swing >= 0 ) { + move = cg.frametime * scale * speed; + if ( move >= swing ) { + move = swing; + *swinging = qfalse; + } + *angle = AngleMod( *angle + move ); + } else if ( swing < 0 ) { + move = cg.frametime * scale * -speed; + if ( move <= swing ) { + move = swing; + *swinging = qfalse; + } + *angle = AngleMod( *angle + move ); + } + + // clamp to no more than tolerance + swing = AngleSubtract( destination, *angle ); + if ( swing > clampTolerance ) + { + *angle = AngleMod( destination - (clampTolerance - 1) ); //clampTolerance + } + else if ( swing < -clampTolerance ) + { + *angle = AngleMod( destination + (clampTolerance - 1) ); //clampTolerance + } +} + +/* +================= +CG_AddPainTwitch +================= +*/ +static void CG_AddPainTwitch( centity_t *cent, vec3_t torsoAngles ) { + int t; + float f; + + t = cg.time - cent->pe.painTime; + if ( t >= PAIN_TWITCH_TIME ) { + return; + } + + f = 1.0 - (float)t / PAIN_TWITCH_TIME; + + if ( cent->pe.painDirection ) { + torsoAngles[ROLL] += 20 * f; + } else { + torsoAngles[ROLL] -= 20 * f; + } +} + + +/* +=============== +CG_PlayerAngles + +Handles seperate torso motion + + legs pivot based on direction of movement + + head always looks exactly at cent->lerpAngles + + if motion < 20 degrees, show in head only + if < 45 degrees, also show in torso +=============== +*/ + +//static float yawClamped; +//static float headClamp; + +#define YAW_DELTA 100 //max yaw a head can turn around without looking like an exorcist spoof ;P +#define PITCH_DELTA 35 //max pitch a head can tilt before looking like the player sepearated their neck O_o + +extern qboolean PM_PlayerIdling ( int torsoAnim, int legsAnim ); + +static void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) { + vec3_t legsAngles, torsoAngles, headAngles; + float dest; + //float delta; + static int movementOffsets[8] = { 0, 22, 45, -22, 0, 22, -45, -22 }; //{ 0, 22, 45, -22, 0, 22, -45, -22 }; + vec3_t velocity; + float speed; + int dir; + qboolean offsetPitch; + clientInfo_t* ci; + int i; + + qboolean LockBodyYaw=qfalse; //RPG-X:TiM + + if ( cent->currentState.eFlags & EF_ITEMPLACEHOLDER ) + ci = &cgs.decoyInfo[cent->currentState.eventParm]; + else + ci = &cgs.clientinfo[cent->currentState.clientNum]; + + if ( cent->currentState.eFlags & EF_CLAMP_ALL ) { + + VectorSet( headAngles, 0, cent->pe.legs.yawAngle, 0 ); + VectorSet( torsoAngles, 0, cent->pe.legs.yawAngle, 0 ); + VectorSet( legsAngles, 0, cent->pe.legs.yawAngle, 0 ); + + AnglesSubtract( headAngles, torsoAngles, headAngles ); + AnglesSubtract( torsoAngles, legsAngles, torsoAngles ); + AnglesToAxis( legsAngles, legs ); + AnglesToAxis( torsoAngles, torso ); + AnglesToAxis( headAngles, head ); + return; + } + + if( /*( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != BOTH_LADDER_DWN1 + && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != BOTH_LADDER_UP1 + && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != BOTH_LADDER_IDLE + &&*/ !( cent->currentState.eFlags & EF_CLAMP_BODY ) ) + { + LockBodyYaw = qfalse; + } + else { + LockBodyYaw = qtrue; + + cent->pe.torso.pitchAngle = 0; + } + //} + + VectorCopy( cent->lerpAngles, headAngles ); + VectorClear( legsAngles ); + VectorClear( torsoAngles ); + headAngles[YAW] = AngleMod( headAngles[YAW] ); + //headAngles[PITCH] = AngleMod( headAngles[PITCH] ); + + /*if ( LockBodyYaw && yawClamped == 0.0 ) + yawClamped = headAngles[YAW]; + else if ( !LockBodyYaw ) { + yawClamped = 0.0; + }*/ + + if ( LockBodyYaw ) { + float deltaYaw; + float turnRatio; + float finalPitch; + + //calc ratio of delta from origin yaw to current yaw + turnRatio = Q_fabs( AngleDelta( cent->pe.torso.yawAngle, headAngles[YAW] ) ) / 90.0f; + if ( turnRatio > 1.0f ) + turnRatio = 1.0f; + if ( turnRatio < 0.0f ) + turnRatio = 0.0f; + + finalPitch = (float)PITCH_DELTA - ( 10.0f * turnRatio ); + + //CG_Printf( "Pitch Before: %f\n", headAngles[PITCH] ); + + //handle head pitch + //if players are looking down, clamp it so the more yaw there is, the higher they'll be looking. + //reason being, most humans can't bury their face that far into their shoulders. + if ( headAngles[PITCH] > finalPitch && headAngles[PITCH] > 0 ) //Looking down. weirdly enough + { + headAngles[PITCH] = finalPitch; + } + else if ( headAngles[PITCH] < -PITCH_DELTA ) + { + //delta = headAngles[YAW] + AngleMod( cent->pe.legs.yawAngle + YAW_DELTA); + //headAngles[YAW] = AngleMod(cent->pe.legs.yawAngle - YAW_DELTA) + delta; + headAngles[PITCH] = -PITCH_DELTA; + } + + //if head yaw is about to rip off the exorcist + /*if ( ( headAngles[YAW] > AngleMod( cent->pe.legs.yawAngle + YAW_DELTA) ) + && ( headAngles[YAW] < AngleMod( cent->pe.legs.yawAngle + 180.0f) ) ) + { + //delta = Q_fabs(headAngles[YAW] - AngleMod( cent->pe.legs.yawAngle + YAW_DELTA)); + //headAngles[YAW] = AngleMod(cent->pe.legs.yawAngle + YAW_DELTA) - delta; + headAngles[YAW] = AngleMod(cent->pe.legs.yawAngle + YAW_DELTA); + } + else if ( headAngles[YAW] < AngleMod( cent->pe.legs.yawAngle - YAW_DELTA) + && ( headAngles[YAW] > AngleMod( cent->pe.legs.yawAngle + 180.0f) ) ) + { + //delta = headAngles[YAW] + AngleMod( cent->pe.legs.yawAngle + YAW_DELTA); + //headAngles[YAW] = AngleMod(cent->pe.legs.yawAngle - YAW_DELTA) + delta; + headAngles[YAW] = AngleMod(cent->pe.legs.yawAngle - YAW_DELTA); + }*/ + + if ( Q_fabs( deltaYaw = AngleDelta( headAngles[YAW], cent->pe.torso.yawAngle ) ) > YAW_DELTA ) { + if ( deltaYaw > 0 ) { + headAngles[YAW] = AngleNormalize360 ( cent->pe.torso.yawAngle + YAW_DELTA ); + } + else { + headAngles[YAW] = AngleNormalize360 ( cent->pe.torso.yawAngle - YAW_DELTA ); + } + } + //CG_Printf( "Pitch After: %f\n", headAngles[PITCH] ); + } + + // --------- yaw ------------- + + // allow yaw to drift a bit + if ( !PM_PlayerIdling( cent->currentState.torsoAnim, cent->currentState.legsAnim ) + || ( cg_liftEnts[cent->currentState.clientNum] > 0/*(cg.time - cgs.levelStartTime)*/ ) ) { + // if not standing still, always point all in the same direction + cent->pe.torso.yawing = qtrue; // always center + cent->pe.torso.pitching = qtrue; // always center + cent->pe.legs.yawing = qtrue; // always center + offsetPitch = qfalse; + } + else { + offsetPitch = qtrue; + } + + // adjust legs for movement dir + if ( cent->currentState.eFlags & EF_DEAD ) { + // don't let dead bodies twitch + dir = 0; + } else { + dir = cent->currentState.angles2[YAW]; + if ( dir < 0 || dir > 7 ) { + CG_Error( "Bad player movement angle" ); + } + } + + //RPG-X Ladder disables character yawing coz it's like they're spinning on air + if( !LockBodyYaw ) + { + legsAngles[YAW] = headAngles[YAW] ; + torsoAngles[YAW] = headAngles[YAW] ; + + if ( !cg_liftEnts[cent->currentState.clientNum] ) + { + legsAngles[YAW] += movementOffsets[ dir ]; + torsoAngles[YAW] += 0.25 * movementOffsets[ dir ]; + } + + CG_SwingAngles( torsoAngles[YAW], 25, 90, cg_swingSpeed.value, ¢->pe.torso.yawAngle, ¢->pe.torso.yawing, qfalse ); + CG_SwingAngles( legsAngles[YAW], 40, 90, cg_swingSpeed.value, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing, qfalse ); + } + + //TiM - if turbolifting, rotate us, but then don't lerp + if ( cg_liftEnts[cent->currentState.clientNum] > 0/*(cg.time - cgs.levelStartTime)*/ ) + { + if ( !cent->pe.legs.yawing ) + { + cent->clampAngles = qtrue; + } + } + + if ( cent->clampAngles ) { + //torsoAngles[YAW] = headAngles[YAW]; + //legsAngles[YAW] = headAngles[YAW]; + cent->pe.torso.yawAngle = headAngles[YAW]; + cent->pe.legs.yawAngle = headAngles[YAW]; + } + + torsoAngles[YAW] = cent->pe.torso.yawAngle; + legsAngles[YAW] = cent->pe.legs.yawAngle; + + if ( !cg_liftEnts[cent->currentState.clientNum] && cent->clampAngles > qfalse ) + cent->clampAngles = qfalse; + + // --------- pitch ------------- + + //TiM : Add an offset so they don't lean as much when idling + //Make it default elsewise tho + // only show a fraction of the pitch angle in the torso + + //Com_Printf( "headPitch: %f\n", headAngles[PITCH] ); + if ( headAngles[PITCH] > 180 ) { + dest = (-360 + headAngles[PITCH]) * (offsetPitch==qtrue ? 0.45 : 0.75); //(offsetPitch ? 0.95 : 0.75) + } else { + //if offsetPitch enabled (ie, we're in the ideal pose for the cool neck-only rotation) + //this will make the actual torso pitch delay a tad until it's passed a threshold + //of 30 degrees in either direction. The overall aim of this is to try and minimize + //the torso movement so it's more realistically subtle, instead of stupidly, physics defyingly + //obvious (like being able to bend on a 90 degree ange >_< ) + if ( offsetPitch ) { + if ( headAngles[PITCH] > 30 ) { + dest = (headAngles[PITCH] - 30 ) * 0.60; + } else if ( headAngles[PITCH] < -40 ) { + dest = ( headAngles[PITCH] + 40 ) * 0.45; + } else { + dest = 0; + } + } + else { + dest = headAngles[PITCH] * 0.75; + } + } + + //I had to lock down the pitch when dead. The player's head was going thru the floor O_o + if( !LockBodyYaw && !( cent->currentState.eFlags & EF_DEAD ) /*&& !( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson )*/ ) + { //(offsetPitch ? 10 : 30) + + if (cent->currentState.eFlags & EF_FULL_ROTATE ) + { + //CG_Printf("Lerp Detected\n"); + legsAngles[PITCH] = headAngles[PITCH]; + CG_SwingAngles( legsAngles[PITCH], 15, 30, 0.1, ¢->pe.legs.pitchAngle, ¢->pe.legs.pitching, qtrue ); + legsAngles[PITCH] = cent->pe.legs.pitchAngle; + + torsoAngles[PITCH] = headAngles[PITCH]; + CG_SwingAngles( torsoAngles[PITCH], 15, 30, 0.1, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching, qtrue ); + torsoAngles[PITCH] = cent->pe.torso.pitchAngle; + } + else + { + CG_SwingAngles( dest, 15, 30, 0.1, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching, qtrue ); + torsoAngles[PITCH] = cent->pe.torso.pitchAngle; + } + } + + //Com_Printf("Bitwise operation >>: %i, Bitwise Operation &: %i\n", Q_log2(8192), 10 & 255 ); + //Com_Printf("Atof: %f\n", atof( "0.43284673 t" ) ); + + // --------- talking ---------- + // ------ head rotation ------- + if ( cent->currentState.eFlags & EF_TALKING ) { + ci->headDebounceTime = cg.time + 1000; + } + + if (ci->headDebounceTime > cg.time /*&& !LockBodyYaw*/ ) { + if ( cent->currentState.eFlags & EF_TALKING ) { + if ( cg.time > ci->nextTalkAngle || (!ci->talkAngles[PITCH] && !ci->talkAngles[YAW] && !ci->talkAngles[ROLL]) ) { + + for ( i = 0; i < 3; i++ ) { + ci->talkAngles[i] = flrandom( -4, 4 ); + } + + ci->talkDifferential = irandom( 200, 500 ); + ci->nextTalkAngle = cg.time + ci->talkDifferential; + } + + } + else { + if ( ci->talkAngles[0] != 1 && ci->talkAngles[1] != 1 && ci->talkAngles[2] != 1 ) { + //VectorCopy(ci->talkAngles, cent->lerpAngles); + ci->talkDifferential = 300; + VectorSet(ci->talkAngles, 0, 0, 0 ); + } + } + //Com_Printf("Yaw Offset: %f, Yaw: %f, LerpYaw: %f\n", ci->talkAngles[YAW], headAngles[YAW], cent->lerpAngles[YAW]); + + //if ( (cent->pe.head.pitchAngle != 0.0 && cent->pe.head.yawAngle != 0.0 && cent->pe.head.yawAngle != 0.0) + // && !VectorCompare( ci->talkAngles, vec3_origin) ) { + + CG_SwingAngles( ci->talkAngles[PITCH], 30, 30, ci->talkDifferential*0.00005, ¢->pe.head.pitchAngle, ¢->pe.head.pitching, qtrue ); + CG_SwingAngles( ci->talkAngles[YAW], 30, 30, ci->talkDifferential*0.00005, ¢->pe.head.yawAngle, ¢->pe.head.yawing, qtrue ); + CG_SwingAngles( ci->talkAngles[ROLL], 30, 30, ci->talkDifferential*0.00005, ¢->pe.head.rollAngle, ¢->pe.head.rolling, qtrue ); + + headAngles[PITCH] = AngleMod(headAngles[PITCH] + cent->pe.head.pitchAngle); + headAngles[YAW] = AngleMod(headAngles[YAW] + cent->pe.head.yawAngle); + headAngles[ROLL] = AngleMod(headAngles[ROLL] + cent->pe.head.rollAngle); + //} + } + + // --------- roll ------------- + //TiM - After I reintegrated velocity into + //the player state, this code randomly started working lol! + + // lean towards the direction of travel + VectorCopy( cent->currentState.pos.trDelta, velocity ); + speed = VectorNormalize( velocity ); + if ( speed ) { + vec3_t axis[3]; + float side; + + speed *= 0.04; //0.05 - TiM + + AnglesToAxis( legsAngles, axis ); + side = speed * DotProduct( velocity, axis[1] ); + legsAngles[ROLL] -= side; + + side = speed * DotProduct( velocity, axis[0] ); + legsAngles[PITCH] += side; + } + + // pain twitch + CG_AddPainTwitch( cent, torsoAngles ); + + // pull the angles back out of the hierarchial chain + AnglesSubtract( headAngles, torsoAngles, headAngles ); + AnglesSubtract( torsoAngles, legsAngles, torsoAngles ); + AnglesToAxis( legsAngles, legs ); + AnglesToAxis( torsoAngles, torso ); + AnglesToAxis( headAngles, head ); +} + + +//========================================================================== + +/* +=============== +CG_HasteTrail +=============== +*/ +/*static void CG_HasteTrail( centity_t *cent ) { + localEntity_t *smoke; + vec3_t origin, pos2; + int anim; + + if ( cent->trailTime > cg.time ) { + return; + } + anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT; + if ( anim != BOTH_RUN1 && anim != BOTH_RUN1 ) { //LEGS_RUN + return; + } + + cent->trailTime += 100; + if ( cent->trailTime < cg.time ) { + cent->trailTime = cg.time; + } + + VectorCopy( cent->lerpOrigin, origin ); + origin[0] += flrandom(-5,5); + origin[1] += flrandom(-5,5); + origin[2] -= 15; + + AngleVectors(cent->lerpAngles, pos2, NULL, NULL); + pos2[2]=0; + VectorMA(origin, -22.0, pos2, pos2); + + smoke = FX_AddLine(origin, pos2, 1.0, 20.0, -12.0, 0.7, 0.0, 500, cgs.media.hastePuffShader); +}*/ + +/* +=============== +CG_FlightTrail +=============== +*/ +/*static void CG_FlightTrail( centity_t *cent ) { + localEntity_t *smoke; + vec3_t origin; + vec3_t vel; + vec3_t startrgb={0.5, 0.5, 0.5}; + vec3_t endrgb={0.0,0.0,0.0}; + + VectorCopy( cent->lerpOrigin, origin ); + origin[2] -= flrandom(10,18); + + VectorSet(vel, flrandom(-10,10), flrandom(-10, 10), flrandom(-30,-50)); + + smoke = FX_AddSprite2(origin, vel, qfalse, 4.0, 4.0, 0.5, 0.0, startrgb, endrgb, flrandom(0,360), 0, 500, cgs.media.flightPuffShader); +}*/ + +/* +=============== +CG_TrailItem +=============== +*/ +static void CG_TrailItem( centity_t *cent, qhandle_t hModel ) { + refEntity_t ent; + vec3_t angles; + float frame; + + if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) + { + return; + } + + memset( &ent, 0, sizeof( ent ) ); + + VectorCopy( cent->lerpAngles, angles ); + angles[PITCH] = 0; + angles[ROLL] = 0; + angles[YAW] += 180.0; // It's facing the wrong way. + AnglesToAxis( angles, ent.axis ); + + VectorMA( cent->lerpOrigin, 12, ent.axis[0], ent.origin ); + ent.origin[2] += 4; + + // Make it animate. + frame = (cg.time / 100.0); + ent.renderfx|=RF_WRAP_FRAMES; + + ent.oldframe = (int)frame; + ent.frame = (int)frame+1; + ent.backlerp = (float)(ent.frame) - frame; + + // if the player is looking at himself in 3rd person, don't show the flag solid, 'cause he can't see!!! + if (cent->currentState.number == cg.snap->ps.clientNum) + { + ent.shaderRGBA[3] = 128; + ent.renderfx |= RF_FORCE_ENT_ALPHA; + } + + VectorScale(ent.axis[0], 0.75, ent.axis[0]); + VectorScale(ent.axis[1], 0.9, ent.axis[1]); + VectorScale(ent.axis[2], 0.9, ent.axis[2]); + ent.nonNormalizedAxes = qtrue; + +#if 0 // This approach is used if you want the item to autorotate. Since this is the flag, we don't. + VectorScale( cg.autoAxis[0], 0.75, ent.axis[0] ); + VectorScale( cg.autoAxis[1], 0.75, ent.axis[1] ); + VectorScale( cg.autoAxis[2], 0.75, ent.axis[2] ); +#endif + + ent.hModel = hModel; + trap_R_AddRefEntityToScene( &ent ); +} + +/* +=============== +CG_PlayerPowerups +=============== +*/ +static void CG_PlayerPowerups( centity_t *cent ) { + int powerups; + + powerups = cent->currentState.powerups; + if ( !powerups ) { + return; + } + + // quad gives a dlight +/* if ( powerups & ( 1 << PW_QUAD ) ) { + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2, 0.2, 1.0 ); + }*/ + + // invul gives a dlight +/* if ( powerups & ( 1 << PW_BOLTON ) ) { + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.8, 0.8, 0.2 ); + }*/ + + // borg adapt gives a dlight + //RPG-X TiM + /*if ( powerups & ( 1 << PW_BEAMING ) ) { + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2, 1.0, 0.2 ); + }*/ + + // flight plays a looped sound +// if ( powerups & ( 1 << PW_FLIGHT ) ) { +// trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.flightSound ); +// } + + // redflag + /*if ( powerups & ( 1 << PW_REDFLAG ) ) { + CG_TrailItem( cent, cgs.media.redFlagModel ); + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1, 0.2, 0.2 ); + }*/ + + // blueflag | RGP-X | GSIO01 | 08/05/2009: no blueflag no more... now borgadapt + if ( powerups & ( 1 << PW_BORG_ADAPT ) ) { + //CG_TrailItem( cent, cgs.media.blueFlagModel ); + //trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2, 0.2, 1 ); + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2, 1.0, 0.2 ); + } + + // haste leaves smoke trails + /*if ( powerups & ( 1 << PW_HASTE ) && (cent->currentState.groundEntityNum==ENTITYNUM_WORLD)) { + CG_HasteTrail( cent ); + }*/ + + // haste leaves smoke trails +// if ( powerups & ( 1 << PW_FLIGHT ) && (cent->currentState.groundEntityNum!=ENTITYNUM_WORLD)) { +// CG_FlightTrail( cent ); +// } + + // seeker coolness + /*if ( powerups & ( 1 << PW_FLASHLIGHT ) ) + { + CG_Seeker(cent); + }*/ +} + + + + +/* +=============== +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; + int team; + + if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) { + rf = RF_THIRD_PERSON; // only show in mirrors + } else { + rf = 0; + } + + team = cgs.clientinfo[ cent->currentState.clientNum ].team; + + memset( &ent, 0, sizeof( ent ) ); + VectorCopy( cent->lerpOrigin, ent.origin ); + ent.origin[2] += 48; + ent.reType = RT_SPRITE; + ent.customShader = shader; + ent.data.sprite.radius = 10; + ent.renderfx = rf; + if (team==TEAM_RED) + { + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 64; + ent.shaderRGBA[2] = 64; + } + else if (team==TEAM_BLUE) + { + ent.shaderRGBA[0] = 64; + ent.shaderRGBA[1] = 64; + ent.shaderRGBA[2] = 255; + } + else + { + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 255; + } + ent.shaderRGBA[3] = 255; + trap_R_AddRefEntityToScene( &ent ); +} + + + +/* +=============== +CG_PlayerSprites + +Float sprites over the player's head +=============== +*/ +static void CG_PlayerSprites( centity_t *cent ) { +// int team; + + if ( cent->currentState.eFlags & EF_CONNECTION ) + { + CG_PlayerFloatSprite( cent, cgs.media.connectionShader ); + return; + } + +/* + if ( cent->currentState.eFlags & EF_TALK ) + { + if ( cgs.clientinfo[cent->currentState.number].pClass == PC_ACTIONHERO ) + { + CG_PlayerFloatSprite( cent, cgs.media.heroSpriteShader ); + } + if ( cgs.clientinfo[cent->currentState.number].pClass == PC_BORG ) + { + if ( (cg_entities[cent->currentState.number].currentState.powerups&(1<currentState.number].pClass == PC_ACTIONHERO ) + { + CG_PlayerFloatSprite( cent, cgs.media.heroSpriteShader ); + return; + } + + //Special hack: if it's Borg who has regen going, must be Borg queen + if ( cgs.clientinfo[cent->currentState.number].pClass == PC_BORG ) + { + if ( (cg_entities[cent->currentState.number].currentState.powerups&(1<currentState.number].pClass == PC_BORG ) + { + CG_PlayerFloatSprite( cent, cgs.media.borgIconShader ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_FIRSTSTRIKE ) { + CG_PlayerFloatSprite( cent, cgs.media.medalFirstStrike ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_IMPRESSIVE ) { + CG_PlayerFloatSprite( cent, cgs.media.medalImpressive ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_EXCELLENT ) { + CG_PlayerFloatSprite( cent, cgs.media.medalExcellent ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_ACE ) { + CG_PlayerFloatSprite( cent, cgs.media.medalAce ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_EXPERT ) { + CG_PlayerFloatSprite( cent, cgs.media.medalExpert ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_MASTER ) { + CG_PlayerFloatSprite( cent, cgs.media.medalMaster ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_CHAMPION ) { + CG_PlayerFloatSprite( cent, cgs.media.medalChampion ); + return; + } + + team = cgs.clientinfo[ cent->currentState.clientNum ].team; + if ( !(cent->currentState.eFlags & EF_DEAD) && + cg.snap->ps.persistant[PERS_TEAM] == team && + cgs.gametype >= GT_TEAM && + cent->currentState.number != cg.snap->ps.clientNum ) // Don't show a sprite above a player's own head in 3rd person. + { + if (team==TEAM_RED) + { + CG_PlayerFloatSprite( cent, cgs.media.teamRedShader ); + } + else if (team==TEAM_BLUE) + { + CG_PlayerFloatSprite( cent, cgs.media.teamBlueShader ); + } + // else don't show an icon. There currently are no other team types. + + return; + }*/ + + //RPG-X: RedTechie - Cloak sprite basiclly other admins will see this only to tell if that player is cloaked and a admin + //if( cent->currentState.powerups & ( 1 << PW_INVIS ) ){ + // CG_PlayerFloatSprite( cent, cgs.media.cloakspriteShader ); + // return; + //} +return; + +} + +/* +=============== +CG_CalcBeamAlpha +By TiM + +Calculates the current point +in a transport cycle so we +can use it as a fade percentage +Used in shadows, and on the player +model itself +=============== +*/ +#define PLAYER_BEAM_FADETIME_DIV 1.0/(float)PLAYER_BEAM_FADETIME + +void CG_CalcBeamAlpha( int powerups, beamData_t *beamData ) { + float beamAlpha = 1.0; + int bTime = 0; + + + if ( ( powerups & ( 1 << PW_BEAM_OUT ) ) || ( powerups & ( 1 << PW_QUAD ) ) ) { + //TiM - SP transporter FX, also base alpha off of phase in transport cycle + //bTime = cg.time - beamData->beamTimeParam; + + bTime = cg.time - beamData->beamTimeParam; + + if (bTime > PLAYER_BEAM_FADE ) { + if ( bTime < ( PLAYER_BEAM_FADE + PLAYER_BEAM_FADETIME) ) { + beamAlpha = (float)( bTime - PLAYER_BEAM_FADE ) * PLAYER_BEAM_FADETIME_DIV; + + //if we're beaming out, invert the alpha value (so we fade out, not in ) + if ( powerups & ( 1 << PW_BEAM_OUT ) ) { + beamAlpha = 1.0 - beamAlpha; + } + + //Com_Printf( "Alpha = %f\n", beamAlpha ); + } + else { + if ( powerups & ( 1 << PW_BEAM_OUT ) ) { + beamAlpha = 0.0; + } + else { + beamAlpha = 1.0; + } + } + } + else { + if ( powerups & ( 1 << PW_BEAM_OUT ) ) { + beamAlpha = 1.0; + } + else { + beamAlpha = 0.0; + } + } + //CG_Printf( "BeamTime: %i, Alpha: %f\n", bTime, beamAlpha ); + } + + beamData->beamAlpha = beamAlpha; +} + +/* +=============== +CG_PlayerShadow + +Returns the Z component of the surface being shadowed + + should it return a full plane instead of a Z? +=============== +*/ +#define SHADOW_DISTANCE 128 +static qboolean CG_PlayerShadow( centity_t *cent, vec3_t origin, float *shadowPlane, float sizeOffset ) { + vec3_t end, mins = {-7, -7, 0}, maxs = {7, 7, 2}; + trace_t trace; + float alpha; + + *shadowPlane = 0; + + if ( cg_shadows.integer == 0 ) { + return qfalse; + } + + // no shadows when invisible + //TiM - handled in two phases. When invis powerup is active, and beyond flash time, or invis is inactvie, and before flashtime + if ( + ( ( cent->currentState.powerups & ( 1 << PW_INVIS ) && cent->cloakTime > 0 && cg.time > cent->cloakTime + Q_FLASH_TIME * 0.5 ) + || ( !(cent->currentState.powerups & ( 1 << PW_INVIS )) && cent->decloakTime > 0 && cg.time < cent->decloakTime + Q_FLASH_TIME * 0.5 ) ) ) + { + return qfalse; + } + + // send a trace down from the player to the ground + //VectorCopy( cent->lerpOrigin, end ); + VectorCopy( origin, end ); + end[2] -= SHADOW_DISTANCE; + + //trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID ); + trap_CM_BoxTrace( &trace, origin, end, mins, maxs, 0, MASK_PLAYERSOLID ); + + // no shadow if too high + if ( trace.fraction == 1.0 ) { + 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; + + //transporter FX - beam alpha + alpha *= cent->beamData.beamAlpha; + + if ( alpha == 0.0 ) { + return qfalse; + } + + // --TiM + + // add the mark as a temporary, so it goes directly to the renderer + // without taking a spot in the cg_marks array + CG_ImpactMark( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal, + cent->pe.legs.yawAngle, 1,1,1,alpha, qfalse, ( 16 * sizeOffset ), 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]; + + float beamRatio = 1.0; + + if ( !cg_shadows.integer ) { + return; + } + + VectorCopy( cent->lerpOrigin, end ); + end[2] -= 24; + + // if the feet aren't in liquid, don't make a mark + // this won't handle moving water brushes, but they wouldn't draw right anyway... + contents = trap_CM_PointContents( end, 0 ); + if ( !( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) ) { + return; + } + + VectorCopy( cent->lerpOrigin, start ); + start[2] += 32; + + // if the head isn't out of liquid, don't make a mark + contents = trap_CM_PointContents( start, 0 ); + if ( contents & ( CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + return; + } + + // trace down to find the surface + trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ); + + if ( trace.fraction == 1.0 ) { + return; + } + + if ( (cent->currentState.powerups & ( 1 << PW_BEAM_OUT ) ) && ( cg.time > ( cent->beamData.beamTimeParam + 2000 ) ) ) { + //beamRatio = 1.0f - (( (float)cg.time - ( (float)cent->beamData.beamTimeParam + 2000.0f ) ) / 2000.0f); + beamRatio = 1.0f - (( (float)cg.time - ( (float)cent->beamData.beamTimeParam + 2000.0f ) ) * 0.0005f); + //CG_Printf( "Beam Out: %f\n", beamRatio ); + } + + if ( cent->currentState.powerups & ( 1 << PW_QUAD ) ) { + //beamRatio = ( ( (float)cg.time - (float)cent->beamData.beamTimeParam ) / 2000.0f ); + beamRatio = ( ( (float)cg.time - (float)cent->beamData.beamTimeParam ) * 0.0005f ); + //CG_Printf( "Beam In: %f\n", beamRatio ); + } + + if ( beamRatio > 1.0 ) { + beamRatio = 1.0; + } + + if ( beamRatio < 0.0 ) { + beamRatio = 0.0; + } + + // create a mark polygon + VectorCopy( trace.endpos, verts[0].xyz ); + verts[0].xyz[0] -= 32 * beamRatio; + verts[0].xyz[1] -= 32 * beamRatio; + 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 * beamRatio; + verts[1].xyz[1] += 32 * beamRatio; + 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 * beamRatio; + verts[2].xyz[1] += 32 * beamRatio; + 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 * beamRatio; + verts[3].xyz[1] -= 32 * beamRatio; + 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 ); +} +static int timestamp; + +/* +=============== +CG_AddRefEntityWithPowerups + +Adds a piece with modifications or duplications for powerups +Also called by CG_Missile for quad rockets, but nobody can tell... +=============== +*/ +void CG_AddRefEntityWithPowerups( refEntity_t *ent, int powerups, int eFlags, beamData_t *beamData, int cloakTime, int decloakTime, qboolean borg ) +{ + + //TiM : No more flickering. Flickering Starfleet officers is bad + /*if ( eFlags & EF_ITEMPLACEHOLDER ) // Hologram Decoy + { + float f1, f2; + + // We used EF_ITEMPLACEHOLDER flag to indicate that this 'player' model + // is actually a holographic decoy. Now there is a chance that the + // decoy will flicker a bit because of ordering with alpha shaders... + + // The lowest the alpha goes is 4.0-2.5-1.0=0.5. + f1 = 4.0 + 2.5*sin(52.423 + cg.time/205.243); + f2 = sin(14.232 + cg.time/63.572); + + f1 = f1+f2; + if (f1 > 1.0) + { // Just draw him solid. + if ( cg.snap->ps.persistant[PERS_CLASS] == PC_TECH ) + {//technicians can see decoys as grids + ent->customShader = cgs.media.rezOutShader; + ent->shaderRGBA[0] = + ent->shaderRGBA[1] = + ent->shaderRGBA[2] = 128; + } + + trap_R_AddRefEntityToScene( ent ); + } + else + { // Draw him faded. + if (f1 > 0.8) + { // Don't have alphas over 0.8, it just looks bad. + f1=0.8; + } + else if (f1 < 0.1) + { + f1=0.1; + } + + ent->renderfx |= RF_FORCE_ENT_ALPHA; // Override the skin shader info and use this alpha value. + ent->shaderRGBA[3] = 255.0*f1; + trap_R_AddRefEntityToScene( ent ); + ent->renderfx &= ~RF_FORCE_ENT_ALPHA; + ent->shaderRGBA[3] = 255; + + // ...with a static shader. + ent->customShader = cgs.media.holoDecoyShader; + ent->shaderRGBA[0] = + ent->shaderRGBA[1] = + ent->shaderRGBA[2] = 255.0*(1.0-f1); // More solid as the player fades out... + trap_R_AddRefEntityToScene(ent); + } + + return; + }*/ + if ((eFlags & EF_DEAD) && (timestamp > cg.time)) + { // Dead. timestamp holds the time of death. + + float alpha; + int a; + + // First draw the entity itself. + //alpha = (timestamp - cg.time)/2500.0; + alpha = (timestamp - cg.time) * 0.0004; + ent->renderfx |= RF_FORCE_ENT_ALPHA; + a = alpha * 255.0; + if (a <= 0) + a=1; + ent->shaderRGBA[3] = a; + trap_R_AddRefEntityToScene( ent ); + ent->renderfx &= ~RF_FORCE_ENT_ALPHA; + ent->shaderRGBA[3] = 255; + + // Now draw the static shader over it. + // Alpha in over half the time, out over half. + alpha = sin(M_PI*alpha); + a = alpha * 255.0; + if (a <= 0) + a=1; + ent->customShader = cgs.media.rezOutShader; + ent->shaderRGBA[0] = + ent->shaderRGBA[1] = + ent->shaderRGBA[2] = a; + trap_R_AddRefEntityToScene( ent ); + ent->shaderRGBA[0] = + ent->shaderRGBA[1] = + ent->shaderRGBA[2] = 255; + } + else if( powerups & ( 1 << PW_BORG_ADAPT ) ) + { + ent->renderfx |= RF_FORCE_ENT_ALPHA; + ent->shaderRGBA[3] = 255; + trap_R_AddRefEntityToScene(ent); + ent->customShader = cgs.media.borgFullBodyShieldShader; + trap_R_AddRefEntityToScene(ent); + return; + } + else if ( powerups & ( 1 << PW_INVIS ) || ( !(powerups & ( 1 << PW_INVIS )) && decloakTime > 0 ) ) + { + if ( ( cloakTime <= 0 && decloakTime <= 0 ) || ( decloakTime > 0 && cg.time < ( decloakTime + Q_FLASH_TIME * 0.5 ) ) + || ( cloakTime > 0 && cg.time > ( cloakTime + Q_FLASH_TIME * 0.5 ) ) ) + { + if ( cgs.clientinfo[cg.snap->ps.clientNum].isAdmin/*cg.snap->ps.persistant[PERS_CLASS] == PC_ADMIN*/ ) + {//admins can see cloaked people + //RPG-X: RedTechie - Pretty Admin Stuff + //ent->customShader = cgs.media.teleportEffectShader; + ent->renderfx |= RF_FORCE_ENT_ALPHA; + ent->shaderRGBA[3] = (unsigned char)(0.4f * 255.0f); + trap_R_AddRefEntityToScene( ent ); + } + else + return; + //ent->customShader = cgs.media.invisShader; //TiM : No point since it's a 100% transparent shader. Use the EF_NODRAW flag instead + } + else + trap_R_AddRefEntityToScene( ent ); + } + else if (powerups & (1<renderfx |= RF_FORCE_ENT_ALPHA; + ent->shaderRGBA[3] = 255 - (dtime)*0.25; + trap_R_AddRefEntityToScene( ent ); + ent->renderfx &= ~RF_FORCE_ENT_ALPHA; + ent->shaderRGBA[3] = 255; + } + + if (dtime < 2000) + { + ent->customShader = cgs.media.disruptorShader; + //ent->shaderTime = timeParam / 1000.0f; + ent->shaderTime = timeParam * 0.001f; + trap_R_AddRefEntityToScene( ent ); + } + } + else if (powerups & (1<renderfx |= RF_FORCE_ENT_ALPHA; + ent->shaderRGBA[3] = (int)(255.0 - (dtime / 300.0) * 254.0); + trap_R_AddRefEntityToScene( ent ); + ent->renderfx &= ~RF_FORCE_ENT_ALPHA; + ent->shaderRGBA[3] = 255; + } + + if (dtime < 500) + { + ent->customShader = cgs.media.explodeShellShader; + ent->renderfx |= RF_CAP_FRAMES; + //ent->shaderTime = timeParam / 1000.0f; + ent->shaderTime = timeParam * 0.001f; + trap_R_AddRefEntityToScene( ent ); + ent->renderfx &= ~RF_CAP_FRAMES; + } + } + else if (powerups & (1<renderfx |= RF_FORCE_ENT_ALPHA; + //ent->shaderRGBA[3] = 100 + 50*sin(cg.time/200.0); + ent->shaderRGBA[3] = 100 + 50*sin(cg.time * 0.005); + trap_R_AddRefEntityToScene( ent ); + ent->renderfx &= ~RF_FORCE_ENT_ALPHA; + ent->shaderRGBA[3] = 255; + } + //SP Transporter Effect + else if ( powerups & ( 1 << PW_BEAM_OUT ) || powerups & ( 1 << PW_QUAD ) ) + { + int btime; + btime = cg.time - beamData->beamTimeParam; + + if ( btime <= PLAYER_BEAM_FADE ) { + if ( powerups & ( 1 << PW_BEAM_OUT ) ) { + ent->shaderRGBA[3] = 255; + } + else { + ent->shaderRGBA[3] = 0; + } + } + else if ( btime >= ( PLAYER_BEAM_FADE + PLAYER_BEAM_FADETIME ) ) { + if ( powerups & ( 1 << PW_BEAM_OUT ) ) { + ent->shaderRGBA[3] = 0; + } + else { + ent->shaderRGBA[3] = 255; + } + } + + if (btime > PLAYER_BEAM_FADE && btime < (PLAYER_BEAM_FADE + PLAYER_BEAM_FADETIME) ) + { + ent->renderfx |= RF_FORCE_ENT_ALPHA; + ent->shaderRGBA[3] = (int)(255 * beamData->beamAlpha); + } + + if ( ent->shaderRGBA[3] > 0 ) { + trap_R_AddRefEntityToScene( ent ); + ent->renderfx &= ~RF_FORCE_ENT_ALPHA; + ent->shaderRGBA[3] = 255; + } + + if ( btime < 4100 ) { + ent->customShader = cgs.media.transportShader; + //ent->shaderTime = beamData->beamTimeParam / 1000.0f; + ent->shaderTime = beamData->beamTimeParam * 0.001f; + trap_R_AddRefEntityToScene( ent ); + } + } + else + { + trap_R_AddRefEntityToScene( ent ); + + //if we did have third person alpha + if ( ent->renderfx & RF_FORCE_ENT_ALPHA ) { + ent->renderfx &= ~RF_FORCE_ENT_ALPHA; + } + // Quad should JUST be on the weapon now, sparky. +/* if ( powerups & ( 1 << PW_QUAD ) ) + { + if (team == TEAM_RED) + ent->customShader = cgs.media.redQuadShader; + else + ent->customShader = cgs.media.quadShader; + trap_R_AddRefEntityToScene( ent ); + } +*/ + + /*if ( powerups & ( 1 << PW_LASER ) ) { + if ( ( ( cg.time / 100 ) % 10 ) == 1 ) { + ent->customShader = cgs.media.regenShader; + trap_R_AddRefEntityToScene( ent ); + } + }*/ + /*if ( powerups & ( 1 << PW_BEAMING )) + { + ent->customShader = cgs.media.borgFullBodyShieldShader; + trap_R_AddRefEntityToScene( ent ); + return; + }*/ +/* if ( powerups & ( 1 << PW_BOLTON )) + { + ent->customShader = cgs.media.battleSuitShader; + trap_R_AddRefEntityToScene( ent ); + return; + }*/ + /*if (powerups & (1 << PW_OUCH)) + { + ent->customShader = cgs.media.holoOuchShader; + // set rgb to 1 of 16 values from 0 to 255. don't use random so that the three + //parts of the player model as well as the gun will all look the same + ent->shaderRGBA[0] = + ent->shaderRGBA[1] = + ent->shaderRGBA[2] = ((cg.time % 17)*0.0625)*255.0;//irandom(0,255); + trap_R_AddRefEntityToScene(ent); + }*/ + + if (powerups & (1<customShader = cgs.media.electricBodyShader; +// ent->shaderTime = timeParam / 1000.0f; + ent->shaderRGBA[0] = + ent->shaderRGBA[1] = + //ent->shaderRGBA[2] = (int)(1.0 + ((4000.0 - dtime) / 4000.0f * 254.0f )); + ent->shaderRGBA[2] = (int)(1.0 + ((4000.0 - dtime) * 0.00025f + 256.0f)); + ent->shaderRGBA[3] = 255; + trap_R_AddRefEntityToScene( ent ); + + if ( random() > 0.95f ) + { + // Play a zap sound to go it. + trap_S_StartSound (ent->origin, entNum, CHAN_AUTO, cg_weapons[WP_DERMAL_REGEN].altHitSound); + } + } + } + } +} + +#define MAX_SHIELD_TIME 2500.0 +#define MIN_SHIELD_TIME 1750.0 + + +void CG_PlayerShieldHit(int entitynum, vec3_t dir, int amount) +{ + centity_t *cent; + int time; + + if (entitynum<0 || entitynum >= MAX_CLIENTS) + { + return; + } + + cent = &cg_entities[entitynum]; + + if (amount > 100) + { + time = cg.time + MAX_SHIELD_TIME; // 2 sec. + } + else + { + time = cg.time + 500 + amount*20; + } + + if (time > cent->damageTime) + { + cent->damageTime = time; + VectorScale(dir, -1, dir); + vectoangles(dir, cent->damageAngles); + } +} + + +/*void CG_DrawPlayerShield(centity_t *cent, vec3_t origin) +{ + refEntity_t ent; + int alpha; + float scale; + + // Don't draw the shield when the player is dead. + if (cent->currentState.eFlags & EF_DEAD) + { + return; + } + + memset( &ent, 0, sizeof( ent ) ); + + VectorCopy( origin, ent.origin ); + ent.origin[2] += 10.0; + AnglesToAxis( cent->damageAngles, ent.axis ); + + alpha = 255.0 * ((cent->damageTime - cg.time) / MIN_SHIELD_TIME) + irandom(0, 16); + if (alpha>255) + alpha=255; + + // Make it bigger, but tighter if more solid + scale = 1.8 - ((float)alpha*(0.4/255.0)); // Range from 1.4 to 1.8 + VectorScale( ent.axis[0], scale, ent.axis[0] ); + VectorScale( ent.axis[1], scale, ent.axis[1] ); + VectorScale( ent.axis[2], scale, ent.axis[2] ); + + ent.hModel = cgs.media.explosionModel; + ent.customShader = cgs.media.halfShieldShader; + ent.shaderRGBA[0] = alpha; + ent.shaderRGBA[1] = alpha; + ent.shaderRGBA[2] = alpha; + ent.shaderRGBA[3] = 255; + trap_R_AddRefEntityToScene( &ent ); +}*/ + + +/*void CG_PlayerHitFX(centity_t *cent) +{ + centity_t *curent; + + // only do the below fx if the cent in question is...uh...me, and it's first person. + if (cent->currentState.clientNum != cg.predictedPlayerState.clientNum || cg.renderingThirdPerson) + { + // Get the NON-PREDICTED player entity, because the predicted one doesn't have the damage info on it. + curent = &cg_entities[cent->currentState.number]; + + if (curent->damageTime > cg.time) + { + CG_DrawPlayerShield(curent, cent->lerpOrigin); + } + + return; + } +}*/ + +//------------------------------------ +/*void CG_BorgEyebeam( centity_t *cent, const refEntity_t *parent ) +{ + qboolean large = qfalse; + vec3_t beamOrg, beamEnd; + trace_t trace; + refEntity_t temp; + + CG_PositionEntityOnTag( &temp, parent, parent->hModel, "tag_ear"); + + if ( VectorCompare( temp.origin, parent->origin )) + { + // Vectors must be the same so the tag_ear wasn't found + return; + } + //Note the above will also prevent the beam from being drawn in first person view if you don't have a tag_ear + + // well, we are in thirdperson or whatnot, so we should just render from a bolt-on ( tag_ear ) + if ( cent->currentState.clientNum != cg.predictedPlayerState.clientNum + || cg.renderingThirdPerson + || cg.snap->ps.pm_type == PM_INTERMISSION ) + { + VectorCopy( temp.origin, beamOrg ); + VectorMA( beamOrg, 1024, temp.axis[0], beamEnd );//forward to end + } + else + { + vec3_t axis[3]; + + // stupid offset hack + AnglesToAxis( cent->lerpAngles, axis ); + VectorMA( cent->lerpOrigin, 26, axis[2], beamOrg );//up + VectorMA( beamOrg, -26, axis[1], beamOrg );//right +// VectorMA( beamOrg, 0.2f, axis[0], beamOrg );//forward + VectorMA( beamOrg, 1024, axis[0], beamEnd );//forward to end + large = qtrue; // render a fatter line + } + + trap_CM_BoxTrace( &trace, beamOrg, beamEnd, NULL, NULL, 0, MASK_SHOT ); + VectorCopy( trace.endpos, beamEnd ); + VectorMA( beamOrg, 0.5, parent->axis[0], beamOrg );//forward + + FX_BorgEyeBeam( beamOrg, beamEnd, trace.plane.normal, large ); +}*/ + +//------------------------------------------------------------------------------ +//Equip mode features + +void CG_AttachHolsters ( centity_t *cent, refEntity_t *parent, int weapon ) +{ + refEntity_t holster; + refEntity_t holsterInner; + + //CG_FlushRefEnt( parent, &holster ); + //CG_FlushRefEnt( parent, &holsterInner ); + + memset( &holster, 0, sizeof( holster ) ); + VectorCopy( parent->lightingOrigin, holster.lightingOrigin ); + holster.shadowPlane = parent->shadowPlane; + holster.renderfx = parent->renderfx; + + memset( &holsterInner, 0, sizeof( holsterInner ) ); + VectorCopy( parent->lightingOrigin, holsterInner.lightingOrigin ); + holsterInner.shadowPlane = parent->shadowPlane; + holsterInner.renderfx = parent->renderfx; + + holster.hModel = (weapon == WP_PHASER) ? cgs.media.phaserHolster : cgs.media.tricorderHolster; + holsterInner.hModel = (weapon == WP_PHASER) ? cgs.media.phaserHolsterInner : cgs.media.tricorderHolsterInner; + + if ( !holster.hModel ) { + return; + } + + CG_PositionEntityOnTag( &holster, parent, parent->hModel, (weapon == WP_PHASER) ? "tag_p_holster" : "tag_t_holster" ); + CG_PositionEntityOnTag( &holsterInner, &holster, holster.hModel, (weapon == WP_PHASER) ? "tag_phaser" : "tag_tricorder" ); + + CG_AddRefEntityWithPowerups( &holster, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, qfalse ); + + if ( cent->currentState.weapon != weapon ) { + CG_AddRefEntityWithPowerups( &holsterInner, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, qfalse ); + } + +} + +void CG_AttachTools ( centity_t *cent, refEntity_t *parent, int weaponNum ) +{ + refEntity_t tool; + weaponInfo_t *weaponInfo; + + weaponInfo = &cg_weapons[weaponNum]; + + //CG_FlushRefEnt( parent, &holster ); + //CG_FlushRefEnt( parent, &holsterInner ); + + memset( &tool, 0, sizeof( tool ) ); + VectorCopy( parent->lightingOrigin, tool.lightingOrigin ); + tool.shadowPlane = parent->shadowPlane; + tool.renderfx = parent->renderfx; + + tool.hModel = weaponInfo->weaponModel; + + if ( !tool.hModel ) { + return; + } + + CG_PositionEntityOnTag( &tool, parent, parent->hModel, (weaponNum == WP_MEDKIT) ? "tag_torso" : "tag_lhand" ); + + if ( cent->currentState.weapon != weaponNum ) { + CG_AddRefEntityWithPowerups( &tool, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, qfalse ); + } + +} +//-------------------------------------------------------------- +//CG version of the flashlight+laser. + +extern qboolean CG_CalcMuzzlePoint( centity_t *cent, vec3_t muzzle, qboolean isDecoy ); + +void CG_AddBeamFX( centity_t *cent ) { + //refEntity_t ent; + trace_t tr; + vec3_t origin, forward, end; + qboolean isDecoy; + + if ( !( cent->currentState.powerups & ( 1 << PW_LASER ) ) && + !( cent->currentState.powerups & ( 1 << PW_FLASHLIGHT ) ) ) + { + return; + } + + isDecoy = (cent->currentState.eFlags & EF_ITEMPLACEHOLDER ); + + //Get origin to render effect + //============================== + //if it's us, we get special treatment, wielding uber-smooth feelings. :) + if ( cent->currentState.clientNum == cg.predictedPlayerState.clientNum && !isDecoy ) { + VectorCopy( cg.predictedPlayerState.origin, origin ); + origin[2] += (float)cg.predictedPlayerState.viewheight * cgs.clientinfo[cg.predictedPlayerState.clientNum].height; + + AngleVectors( cg.predictedPlayerState.viewangles, forward, NULL, NULL ); + } + else { //else, we'll employ a rather hacky (but Official Ravensoft made) way to guess the data we'll need here on CG + if ( CG_CalcMuzzlePoint( cent, origin, isDecoy ) ) + if ( !isDecoy ) + AngleVectors( cent->lerpAngles, forward, NULL, NULL ); + else + AngleVectors( cent->currentState.angles, forward, NULL, NULL ); + else + return; + } + + //perform the trace + VectorMA( origin, 8192, forward, end ); + CG_Trace( &tr, origin, NULL, NULL, end, cent->currentState.clientNum, CONTENTS_SOLID ); + + if (tr.surfaceFlags & SURF_NOIMPACT || tr.surfaceFlags & SURF_SKY ) { + return; + } + + //offset back in a little, so we can actually see the ent lol + VectorMA( tr.endpos, -0.5, forward, end ); + + //============================== + + if ( cent->currentState.powerups & ( 1 << PW_LASER ) && cent->beamData.beamTimeParam == 0 ) { + /*// create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( end, ent.origin); + + ent.reType = RT_SPRITE; + //ent.radius = 2; + //ent.rotation = 0; + ent.data.sprite.radius = 2; + ent.customShader = cgs.media.laserShader; + trap_R_AddRefEntityToScene( &ent );*/ + + //TiM: based on the shadow code instead. This is so the sprite + //is always aligned to the normal of the surface it hits, making it + //act more like a fricken laser. :) + CG_ImpactMark( cgs.media.laserShader, end, tr.plane.normal, + 0,1,1,1,1, qfalse, 2, qtrue ); + } + + if ( cent->currentState.powerups & ( 1 << PW_FLASHLIGHT ) && cent->beamData.beamTimeParam == 0 ) { + vec3_t delta; + float dist, intensity; + + //TiM - calc radius based on distance. + //So it'll simulate real flashlights getting bigger/smaller the closer u are + VectorSubtract( end, origin, delta ); + dist = VectorNormalize( delta ); + //intensity = ( dist / 256.0f ); + intensity = ( dist * 0.00390625f ); + + if ( intensity > 1.0f ) + intensity = 1.0f; + if ( intensity < 0.3f ) + intensity = 0.3f; + + trap_R_AddLightToScene( end, intensity * 200.0f, 1, 1, 1); //200 + } +} + +//-------------------------------------------------------------- + +/* +=============== +CG_Player +=============== +*/ +#define EYES_BLINK_TIME 250 +void CG_Player( centity_t *cent ) { + clientInfo_t *ci; + refEntity_t legs; + refEntity_t torso; + refEntity_t head; + int clientNum; + int renderfx; + qboolean shadow, borg = qfalse; + float shadowPlane = 0; + //alpha value + float alpha = cg_thirdPersonAlpha.value; + int i = 0; + rankModelData_t* rankModelData = NULL; + qboolean rankDataValidated = qfalse; + qboolean isDecoy = qfalse; + + // the client number is stored in clientNum. It can't be derived + // from the entity number, because a single client may have + // multiple corpses on the level using the same clientinfo + clientNum = cent->currentState.clientNum; + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + CG_Error( "Bad clientNum on player entity"); + } + + isDecoy = cent->currentState.eFlags & EF_ITEMPLACEHOLDER; + + //if we're a decoy, use the decoy info instead + if ( isDecoy ) + ci = &cgs.decoyInfo[ cent->currentState.eventParm ]; + else + ci = &cgs.clientinfo[ clientNum ]; + + // it is possible to see corpses from disconnected players that may + // not have valid clientinfo + if ( !ci->infoValid ) { + return; + } + + //borg = ( ci->pClass == PC_BORG ? qtrue : qfalse ); + borg = qfalse; + + // If I'm a borg and there is another borg teleporting, at least allow me to see a trail, then return + /*if ( cg.snap->ps.persistant[PERS_CLASS] == PC_BORG && borg && ( cent->currentState.eFlags & EF_NODRAW )) + { + FX_BorgTeleportTrails( cent->lerpOrigin ); + return; + }*/ + + // add the talk baloon or disconnect icon (not in intermission) + if ( !cg.intermissionStarted ) + { + //RPG-X: RedTechie - Make sure there a admin before printing sprites above there head + if(cgs.clientinfo[cg.snap->ps.clientNum].isAdmin/*cg.snap->ps.persistant[PERS_CLASS] == PC_ADMIN*/){ + CG_PlayerSprites( cent ); + } + } + + //TiM Little hack for RGBA shader rendering + //if we're completely invisible... save us some processing power lol + if ( alpha <= 0.0 ) { + return; + } + + if (cent->currentState.eFlags & EF_NODRAW) + { // Don't draw anymore... + return; + } + + memset( &legs, 0, sizeof(legs) ); + memset( &torso, 0, sizeof(torso) ); + memset( &head, 0, sizeof(head) ); + + //TiM : Manually refresh anims here to make sure it's updated around the board. + //Previously, new players would be loaded, but anim data wouldn't, resulting in really weird looking poses. + //Absolutely flushing all current animation data, forcing the code to reset is the only way I managed to get it working O_o + if ( !ci->animsFlushed ) { + lerpFrame_t *lf; + lerpFrame_t *tf; + + lf = ¢->pe.legs; + tf = ¢->pe.torso; + + lf->animation = tf->animation = NULL; + lf->animationNumber = tf->animationNumber = 0; + lf->animationTime = tf->animationTime = 0; + lf->backlerp = tf->backlerp = 0; + lf->frame = tf->frame = 0; + lf->frameTime = tf->frameTime = 0; + lf->oldFrame = tf->oldFrame = 0; + lf->oldFrameTime = tf->oldFrameTime = 0; + + //TiM : As much as I'd love to do this, it'll delete current angles too, + //which results in players abruptly turning sharply when changing models. + //And might possibly make corpses do the same... which would look creepy lol + + //memset( ¢->pe.legs, 0, sizeof ( cent->pe.legs ) ); + //memset( ¢->pe.torso, 0, sizeof ( cent->pe.torso ) ); + ci->animsFlushed = qtrue; + } + + // get the rotation information + CG_PlayerAngles( cent, legs.axis, torso.axis, head.axis ); + + // get the animation state (after rotation, to allow feet shuffle) + CG_PlayerAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp, + &torso.oldframe, &torso.frame, &torso.backlerp ); + + // add powerups floating behind the player + CG_PlayerPowerups( cent ); + + //backup catch for the lift entity + if ( cg_liftEnts[cent->currentState.clientNum] > 0 && cg_liftEnts[cent->currentState.clientNum] < (cg.time - cgs.levelStartTime) ) + cg_liftEnts[cent->currentState.clientNum] = 0; + + //========================================================== + + /*CG_CalcBeamAlpha( cent->currentState.powerups, ¢->beamData ); + + // add the shadow + if ( !(cent->currentState.eFlags & EF_ITEMPLACEHOLDER) ) + { + shadow = CG_PlayerShadow( cent, &shadowPlane, ci->height ); + } + else + { // - unless we are a hologram... + shadow=qfalse; + shadowPlane=0; + }*/ + + // add a water splash if partially in and out of water + //CG_PlayerSplash( cent ); + + // get the player model information + renderfx = 0; + + if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson && !cg_firstPersonBody.integer && !isDecoy ) { + renderfx |= RF_THIRD_PERSON; // only draw in mirrors + } + + /*if ( cg_shadows.integer == 3 && shadow ) { + renderfx |= RF_SHADOW_PLANE; + }*/ + + renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all + + //append this flag if zooming thru the TR-116. + //It'll render players over everything else. Not quite as good as the TV 116, but it'll do the job. :) + if ( cg.snap->ps.weapon == WP_TR116 && cg.zoomed ) { + renderfx |= RF_DEPTHHACK; //This flag makes making haxx for EF SO EASY!!!! O_O!!!!! + } + + // if we've been hit, display proper fullscreen fx + //CG_PlayerHitFX(cent); + + // If we are dead, set up the correct fx. + if (cent->currentState.eFlags & EF_DEAD) + { + if (cent->deathTime==0) + cent->deathTime = cg.time; // Set Death Time so you can see yourself + + if (cent->currentState.time > cg.time) + { // Fading out. + timestamp=cent->currentState.time; + shadow = qfalse; + shadowPlane = 0; + } + else + { + timestamp = 0; + } + } + else + cent->deathTime = 0; + + // + // add the legs + // + legs.hModel = ci->legsModel; + legs.customSkin = ci->legsSkin; + + VectorCopy( cent->lerpOrigin, legs.origin ); + + //TiM - A cheap override for some things. + //If emoting, offset the player model. Good for solid chairs n stuff + //CG_Printf( "Offset = %f\n", ci->modelOffset ); + if ( ci->modelOffset != 0 ) { + if ( cent->currentState.eFlags & EF_CLAMP_BODY || cent->currentState.eFlags & EF_CLAMP_ALL || cent->currentState.eFlags & EF_ITEMPLACEHOLDER ) { + //TiM: A bit of a hacky override. We don't want this on ladder climbing, yet the emote code + //now forms the basis of the ladder clamp code + if ( (cent->currentState.legsAnim & ~ANIM_TOGGLEBIT) != BOTH_LADDER_DWN1 && + (cent->currentState.legsAnim & ~ANIM_TOGGLEBIT) != BOTH_LADDER_UP1 && + (cent->currentState.legsAnim & ~ANIM_TOGGLEBIT) != BOTH_LADDER_IDLE ) + { + vec3_t offset; + vec3_t legsYaw; + + VectorSet( legsYaw, 0, cent->pe.legs.yawAngle, 0 ); + + AngleVectors( legsYaw, offset, NULL, NULL ); + + VectorMA( legs.origin, ci->modelOffset, offset, legs.origin ); + } + } + } + + VectorCopy( cent->lerpOrigin, legs.lightingOrigin ); + //legs.shadowPlane = shadowPlane; + legs.renderfx = renderfx; + + legs.nonNormalizedAxes = qtrue; + VectorScale( legs.axis[0], ci->height, legs.axis[0]); + VectorScale( legs.axis[1], (ci->height * ci->weight), legs.axis[1]); //weight... i think + VectorScale( legs.axis[2], ci->height, legs.axis[2]); + legs.origin[2] = legs.origin[2] - (24.0f * (1.0f - ci->height)); + //Com_Printf( "weight = %f, race = %s\n", ci->weight, ci->race); + + if ( cg.predictedPlayerState.clientNum == cent->currentState.clientNum && cg.renderingThirdPerson && cg_thirdPersonAlpha.value != 1.0 && !isDecoy ) { + legs.renderfx |= RF_FORCE_ENT_ALPHA; + legs.shaderRGBA[3] = (unsigned char)(alpha * 255.0f); + } + + VectorCopy (legs.origin, legs.oldorigin); // don't positionally lerp at all + + // Setup the param, in case it is needed. + timeParam = cent->deathTime; + entNum = cent->currentState.number; + //CG_AddRefEntityWithPowerups( &legs, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, borg ); + + // if the model failed, allow the default nullmodel to be displayed + if (!legs.hModel) { + return; + } + + // + // add the torso + // + torso.hModel = ci->torsoModel; + if (!torso.hModel) { + return; + } + + torso.customSkin = ci->torsoSkin; + + VectorCopy( cent->lerpOrigin, torso.lightingOrigin ); + + CG_PositionRotatedEntityOnTag( &torso, &legs, ci->legsModel, "tag_torso" ); + + torso.nonNormalizedAxes = qtrue; + torso.shadowPlane = shadowPlane; + torso.renderfx = renderfx; + + if ( cg.predictedPlayerState.clientNum == cent->currentState.clientNum && cg.renderingThirdPerson && cg_thirdPersonAlpha.value != 1.0 ) { + torso.renderfx |= RF_FORCE_ENT_ALPHA; + torso.shaderRGBA[3] = (unsigned char)(alpha * 255.0f); + } + + //TiM - If first person rendering, offset the view vector + if ( !isDecoy && cent->currentState.clientNum == cg.snap->ps.clientNum && cg_firstPersonBody.integer && !cg.renderingThirdPerson /*&& + !( cent->currentState.eFlags & EF_CLAMP_BODY )*/ ) { + vec3_t legsYaw; + vec3_t offset; + + if ( cent->currentState.legsAnim != cg.fpsBody.anim ) { + refEntity_t ref; + vec3_t temp; + + CG_PositionEntityOnTag( &ref, &torso, torso.hModel, "tag_head"); + VectorSubtract( cg.refdef.vieworg, ref.origin, temp ); + + //make the body smaller if need be + //if ( cg.refdef.vieworg[2] < ref.origin[2] ) + //{ + // cg.fpsBody.sizeOffset = (cg.refdef.vieworg[2] - legs.origin[2]) / (ref.origin[2] - legs.origin[2]); + //} + //else + // cg.fpsBody.sizeOffset = 0.0f; + + cg.fpsBody.sizeOffset = 0.9f; + + //normalize, but don't take Z-axis (vertical) into account + //cg.fpsBody.offset = 0 + (int)sqrt( (temp[0]*temp[0]) + (temp[1]*temp[1]) ); + cg.fpsBody.offset = 2; + + cg.fpsBody.anim = cent->currentState.legsAnim ; //+15 for good measure :P + } + + if ( cg.fpsBody.offset > 0.0f ) + { + VectorSet( legsYaw, 0, cent->pe.legs.yawAngle, 0 ); + AngleVectors( legsYaw, offset, NULL, NULL ); + VectorMA( legs.origin, -cg.fpsBody.offset, offset, legs.origin ); + VectorMA( torso.origin, -cg.fpsBody.offset, offset, torso.origin ); + } + + if ( cg.fpsBody.sizeOffset > 0.0f ) + { + VectorScale( legs.axis[0], cg.fpsBody.sizeOffset, legs.axis[0] ); + VectorScale( legs.axis[1], cg.fpsBody.sizeOffset, legs.axis[1] ); + VectorScale( legs.axis[2], cg.fpsBody.sizeOffset, legs.axis[2] ); + VectorScale( torso.axis[0], cg.fpsBody.sizeOffset, torso.axis[0] ); + VectorScale( torso.axis[1], cg.fpsBody.sizeOffset, torso.axis[1] ); + VectorScale( torso.axis[2], cg.fpsBody.sizeOffset, torso.axis[2] ); + + legs.origin[2] = legs.origin[2] - (24.0f * (1.0f - cg.fpsBody.sizeOffset)); + torso.origin[2] = torso.origin[2] - (24.0f * (1.0f - cg.fpsBody.sizeOffset)); + } + + //CG_Printf( "Anim: %i Offset: %i\n", cg.fpsBody.anim, cg.fpsBody.offset ); + } + + //CG_AddRefEntityWithPowerups( &torso, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, borg ); + + // + // add the head + // + head.hModel = ci->headModel; + if (!head.hModel) { + return; + } + + head.customSkin = ci->headSkin; + + if ( !cg_noFrowningHeads.integer && cent->currentState.eFlags & EF_EYES_ANGRY && ci->headSkinFrown ) { + head.customSkin = ci->headSkinFrown; + } + + //BLINK CODE + //if we have a valid blink skin... + //Or we're frowning and we have a frown blink skin + if ( !cg_noBlinkingHeads.integer && ci->headSkinBlink + || + ( !cg_noFrowningHeads.integer && + ( cent->currentState.eFlags & EF_EYES_ANGRY ) + && ci->headSkinFrownBlink ) + ) + { + + //if time exceeded blink time and length of time to allocate blink + //Validate that the blink time won't cause errors + if ( ( ci->headBlinkTime.minSeconds < ci->headBlinkTime.maxSeconds ) ) { + if ( cg.time > ci->headBlinkTime.nextTime ) { + //if blink time has been exceeded, assign a new one + ci->headBlinkTime.nextTime = ( irandom( ci->headBlinkTime.minSeconds, ci->headBlinkTime.maxSeconds ) * 1000 ) + cg.time; + } + } + + //if time has exceeded next blink time, and is still under 250 from then + //Or if we've kicked t3h bucket and we're stone dead right now + //or if we emoted our eyes shut + if ( ( ( cent->currentState.eFlags & EF_EYES_SHUT ) + || ( cent->currentState.eFlags & EF_DEAD ) + || ( cg.time < ci->headBlinkTime.nextTime && cg.time > ci->headBlinkTime.nextTime - EYES_BLINK_TIME ) ) + ) + { + if ( cent->currentState.eFlags & EF_EYES_ANGRY ) + { + if ( ci->headSkinFrownBlink ) + head.customSkin = ci->headSkinFrownBlink; + } + else + { + if ( ci->headSkinBlink ) + head.customSkin = ci->headSkinBlink; + } + } + //Com_Printf( "Max: %i, Min: %i, NextTime: %i, Time: %i\n", ci->headBlinkTime.maxSeconds, ci->headBlinkTime.minSeconds, ci->headBlinkTime.nextTime, cg.time ); + } + + //TALK CODE + if ( !cg_noTalkingHeads.integer && (cent->currentState.eFlags & EF_TALKING) && ci->headSkinTalk[0] ) { + if (cg.time > ci->nextTalkTime ) { + + while ( i == ci->currentTalkSkin ) { + i = irandom( 0, 3 ); + } + ci->currentTalkSkin = i; + ci->nextTalkTime = cg.time + irandom( 100, 150 ); + } + if ( ci->headSkinTalk[ci->currentTalkSkin] ) + head.customSkin = ci->headSkinTalk[ci->currentTalkSkin]; + } + + VectorCopy( cent->lerpOrigin, head.lightingOrigin ); + + CG_PositionRotatedEntityOnTag( &head, &torso, ci->torsoModel, "tag_head" ); + + head.nonNormalizedAxes = qtrue; + head.shadowPlane = shadowPlane; + head.renderfx = renderfx; + + if ( cg.predictedPlayerState.clientNum == cent->currentState.clientNum && cg.renderingThirdPerson && cg_thirdPersonAlpha.value != 1.0 ) { + head.renderfx |= RF_FORCE_ENT_ALPHA; + head.shaderRGBA[3] = (unsigned char)(alpha * 255.0f); + } + + if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson && cg_firstPersonBody.integer ) + head.renderfx |= RF_THIRD_PERSON; + //CG_AddRefEntityWithPowerups( &head, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, borg ); + + /*if ( borg && cgs.pModAssimilation ) + { + CG_BorgEyebeam( cent, &head ); + }*/ + + //player must have just uncloaked. Do the reverse effect + if ( !(cent->currentState.powerups & ( 1 << PW_INVIS )) && cent->wasCloaked ) { + //play flash VFX + if ( cent->decloakTime > 0 ) { + if ( cg.time < cent->decloakTime + Q_FLASH_TIME ) { + //calc the middle waypoint for the model + vec3_t mins, maxs, headTop, avg; + + trap_R_ModelBounds( head.hModel, mins, maxs ); + VectorCopy( head.origin, headTop ); + headTop[2] += ( maxs[2] - mins[2] ); + + VectorCopy( cent->lerpOrigin, mins ); + mins[2] -= 12; + + VectorCopy( headTop, maxs ); + + VectorAverage( mins, maxs, avg ); + + if(!ci->silentCloak) { + //play the VFX + FX_qFlash( cent, avg, cent->decloakTime ); + } + } + else { + if ( cent->wasCloaked ) { + cent->wasCloaked = qfalse; + } + } + } + + if ( cent->decloakTime <= 0 ) { + cent->decloakTime = cg.time; + if(!ci->silentCloak) { + trap_S_StartSound ( NULL, cent->currentState.clientNum, CHAN_AUTO, cgs.media.qFlash ); + } + } + } + else { + if ( cent->decloakTime >= 0 ) { + cent->decloakTime = -1; + } + } + + //Get the index for when we cloaked. We'll use this to stay visible for a split second after we cloaked to enable to proper Q-Effect + //Player just cloaked + if ( cent->currentState.powerups & ( 1 << PW_INVIS ) ) { + if ( cent->cloakTime > 0 && cg.time < cent->cloakTime + Q_FLASH_TIME) { + //calc the middle waypoint for the model + vec3_t mins, maxs, headTop, avg; + + trap_R_ModelBounds( head.hModel, mins, maxs ); + VectorCopy( head.origin, headTop ); + headTop[2] += ( maxs[2] - mins[2] ); + + VectorCopy( cent->lerpOrigin, mins ); + mins[2] -= 12; + + VectorCopy( headTop, maxs ); + + VectorAverage( mins, maxs, avg ); + + if(!ci->silentCloak) { + //play the VFX + FX_qFlash( cent, avg, cent->cloakTime ); + } + + } + + if ( cent->cloakTime <= 0 ) { + cent->wasCloaked = qtrue; + cent->cloakTime = cg.time; + if(!ci->silentCloak) { + trap_S_StartSound ( NULL, cent->currentState.clientNum, CHAN_AUTO, cgs.media.qFlash ); + } + } + } + else { + if ( cent->cloakTime >= 0 ) + cent->cloakTime = -1; + } + + //Like EF_DEAD, but when we're beaming + //========================================================= + if ( cent->currentState.powerups & ( 1 << PW_BEAM_OUT ) /*&& !( cent->currentState.powerups & ( 1 << PW_INVIS ) )*/ ) { + vec3_t mins, maxs, headTop; + + if (cent->beamData.beamTimeParam == 0 && !cent->beamData.beamInDetected ) { + cent->beamData.beamTimeParam = cg.time; + CG_AddFullScreenEffect( SCREENFX_SP_TRANSPORTER_OUT, clientNum ); + } + + //Calculate the bounding region of head so beam flares will end there + trap_R_ModelBounds( head.hModel, mins, maxs ); + VectorCopy( head.origin, headTop ); + headTop[2] += ( maxs[2] - mins[2] ); + + if ( !(cent->currentState.powerups & ( 1 << PW_INVIS )) ) + FX_SPTransporterLensFlares( cent, headTop, cent->beamData.beamTimeParam ); + } + else { + if ( !cent->beamData.beamInDetected ) { + cent->beamData.beamTimeParam = 0; + } + } + + + if ( cent->currentState.powerups & ( 1 << PW_QUAD ) && !( cent->currentState.powerups & ( 1 << PW_INVIS ) ) ) { + vec3_t mins, maxs, headTop; + + if ( cent->beamData.beamInDetected == qfalse ) { + cent->beamData.beamTimeParam = cg.time; + cent->beamData.beamInDetected = qtrue; + CG_AddFullScreenEffect( SCREENFX_SP_TRANSPORTER_IN, clientNum ); + + //normally player ents reset themselves... but we don't to ourselves >.< + //so do that now + if ( cent->currentState.clientNum == cg.snap->ps.clientNum ) { + CG_ResetPlayerEntity( cent ); + } + } + + trap_R_ModelBounds( head.hModel, mins, maxs ); + VectorCopy( head.origin, headTop ); + headTop[2] += ( maxs[2] - mins[2] ); + + FX_SPTransporterLensFlares( cent, headTop, cent->beamData.beamTimeParam ); + } + else { + if ( cent->beamData.beamInDetected ) { + cent->beamData.beamTimeParam = 0; + cent->beamData.beamInDetected = qfalse; + } + } + //========================================================= + + CG_CalcBeamAlpha( cent->currentState.powerups, ¢->beamData ); + + // add the shadow + if ( /*!(cent->currentState.eFlags & EF_ITEMPLACEHOLDER)*/ qtrue ) + { + shadow = CG_PlayerShadow( cent, legs.origin, &shadowPlane, ci->height ); + } + else + { // - unless we are a hologram... + shadow=qfalse; + shadowPlane=0; + } + + if ( cg_shadows.integer == 3 && shadow ) { + legs.renderfx |= RF_SHADOW_PLANE; + torso.renderfx |= RF_SHADOW_PLANE; + head.renderfx |= RF_SHADOW_PLANE; + } + + //TiM : Shadow calc must appear after beam code, or else we get intermittent glitches on transitioning + legs.shadowPlane = shadowPlane; + torso.shadowPlane = shadowPlane; + head.shadowPlane = shadowPlane; + + //TiM - Added here so it's better in the hierarchy + CG_PlayerSplash( cent ); + + CG_AddRefEntityWithPowerups( &legs, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, borg ); + CG_AddRefEntityWithPowerups( &torso, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, borg ); + CG_AddRefEntityWithPowerups( &head, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, borg ); + + //TiM: Add player based interpolated vfx such as flashlight and the fricken laser. :) + CG_AddBeamFX( cent ); + + // + // add the gun / barrel / flash + // + CG_AddPlayerWeapon( &torso, NULL, cent ); + + //============================================================================= + //TR-116 EyeScope!! :) + // + if ( cent->currentState.weapon == WP_TR116 ) + { + refEntity_t eyeScope; + + memset( &eyeScope, 0, sizeof( eyeScope ) ); + VectorCopy( cent->lerpOrigin, eyeScope.lightingOrigin ); + eyeScope.shadowPlane = shadowPlane; + eyeScope.renderfx = renderfx; + eyeScope.hModel = cgs.media.tr116EyeScope; + + if ( !isDecoy && cent->currentState.clientNum == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) + eyeScope.renderfx |= RF_THIRD_PERSON; + + if ( eyeScope.hModel ) { + CG_PositionEntityOnTag( &eyeScope, &head, head.hModel, "tag_eyescope" ); + + if ( !VectorCompare( eyeScope.origin, head.origin ) ) { + CG_AddRefEntityWithPowerups( &eyeScope, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, borg ); + } + } + + } + + //SIMS Module + if ( cent->currentState.powerups & ( 1 << PW_FLASHLIGHT ) ) + { + refEntity_t flashlight; + + memset( &flashlight, 0, sizeof( flashlight ) ); + VectorCopy( cent->lerpOrigin, flashlight.lightingOrigin ); + flashlight.shadowPlane = shadowPlane; + flashlight.renderfx = renderfx; + flashlight.hModel = cgs.media.simsModule; + + if ( !isDecoy && cent->currentState.clientNum == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) + flashlight.renderfx |= RF_THIRD_PERSON; + + if ( flashlight.hModel ) { + CG_PositionEntityOnTag( &flashlight, &head, head.hModel, "tag_flashlight" ); + + if ( !VectorCompare( flashlight.origin, head.origin ) ) { + CG_AddRefEntityWithPowerups( &flashlight, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, borg ); + } + } + } + + //medical scanner + if ( cgs.classData[ci->pClass].isMedic && cent->currentState.weapon == WP_TRICORDER ) + { + refEntity_t scanner; + + memset( &scanner, 0, sizeof( scanner ) ); + VectorCopy( cent->lerpOrigin, scanner.lightingOrigin ); + scanner.shadowPlane = shadowPlane; + scanner.renderfx = renderfx; + scanner.hModel = cgs.media.medicalScanner; + + if ( scanner.hModel ) { + CG_PositionEntityOnTag( &scanner, &torso, torso.hModel, "tag_lhand" ); + + if ( !VectorCompare( scanner.origin, torso.origin ) ) { + CG_AddRefEntityWithPowerups( &scanner, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, borg ); + } + } + } + + // + + //============================================================================= + // + // attach teh holsters + // + if ( cent->currentState.powerups & ( 1 << PW_BOLTON ) ) + { + if ( ci->isHazardModel && !( !cg.renderingThirdPerson && cent->currentState.clientNum == cg.snap->ps.clientNum ) ) + { + refEntity_t hazardHelmet; + + memset( &hazardHelmet, 0, sizeof( hazardHelmet ) ); + VectorCopy( cent->lerpOrigin, hazardHelmet.lightingOrigin ); + hazardHelmet.shadowPlane = shadowPlane; + hazardHelmet.renderfx = renderfx; + hazardHelmet.hModel = cgs.media.hazardHelmet; + + if ( cgs.media.hazardHelmet ) + { + CG_PositionEntityOnTag( &hazardHelmet, &torso, torso.hModel, "tag_head" ); + + //derive new rotation axis from head model + //else the helmet doesn't rotate with the head, wielding funny results + AxisCopy( head.axis, hazardHelmet.axis ); + + if ( !VectorCompare( hazardHelmet.origin, vec3_origin ) ) + { + CG_AddRefEntityWithPowerups( &hazardHelmet, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, borg ); + } + } + } + else if ( ci->holsterModel ) + { + refEntity_t holsterTags; + + memset( &holsterTags, 0, sizeof(holsterTags) ); + VectorCopy( cent->lerpOrigin, holsterTags.lightingOrigin ); + holsterTags.shadowPlane = shadowPlane; + holsterTags.renderfx = renderfx; + holsterTags.hModel = ci->holsterModel; + + if ( holsterTags.hModel ) { + + CG_PositionEntityOnTag( &holsterTags, &legs, legs.hModel, "tag_torso" ); + + trap_R_AddRefEntityToScene( &holsterTags ); + + //TiM - Added an extra bit of data to entitystate, so now, the game + //should transmit each player's weapon list here, so we can make it dynamic now + + //if player has the phaser, bolt it + if ( cent->currentState.time2 & 1 << WP_PHASER ) { + CG_AttachHolsters( cent, &holsterTags, WP_PHASER ); + } + + //if player has tricorder + if ( cent->currentState.time2 & 1 << WP_TRICORDER ) { + CG_AttachHolsters( cent, &holsterTags, WP_TRICORDER ); + } + + //if player has medkit + if ( cent->currentState.time2 & 1 << WP_MEDKIT ) { + CG_AttachTools( cent, &torso, WP_MEDKIT ); + } + + //if player has toolkit + if ( cent->currentState.time2 & 1 << WP_TOOLKIT ) { + CG_AttachTools( cent, &torso, WP_TOOLKIT ); + } + + /*switch ( ci->pClass ) { + case PC_COMMAND: + case PC_ENGINEER: + case PC_SECURITY: + case PC_SCIENCE: + case PC_ADMIN: + CG_AttachHolsters( cent, &holsterTags, WP_PHASER ); + break; + } + + switch ( ci->pClass ) { + case PC_ENGINEER: + case PC_SECURITY: + case PC_SCIENCE: + case PC_MEDICAL: + case PC_ADMIN: + CG_AttachHolsters( cent, &holsterTags, WP_TRICORDER ); + break; + } + + switch ( ci->pClass ) { + case PC_ENGINEER: + CG_AttachTools( cent, &torso, WP_TOOLKIT ); + break; + } + + switch ( ci->pClass ) { + case PC_MEDICAL: + CG_AttachTools( cent, &torso, WP_MEDKIT ); + break; + }*/ + } + } + } + //============================================================================= + // + //Custom Boltons + + //Com_Printf("Index: %i, Name: %s, Handle: %i\n", ci->boltonTags[0].modelBase, ci->boltonTags[0].tagName, ci->boltonTags[0].tagModel); + + //If we have boltons... + if ( ci->boltonTags[0].tagModel ) { //We'll test by seeing if a model was cached into the first slot + refEntity_t boltOns[MAX_BOLTONS]; + refEntity_t* target = NULL; + qhandle_t targetModel; + + for (i = 0; ( ( i < MAX_BOLTONS ) || ( ci->boltonTags[i].tagModel || ci->boltonTags[i].tagName[0] ) ); i++ ) { + //if there's no data in there... no point to rendering it + //ROFL... uh, we can't use .modelBase since 0 is a valid entry for that one :P + if ( !ci->boltonTags[i].tagName[0] || !ci->boltonTags[i].tagModel ) { + break; + } + + memset( &boltOns[i], 0, sizeof(boltOns[i]) ); + VectorCopy( cent->lerpOrigin, boltOns[i].lightingOrigin ); + boltOns[i].renderfx = renderfx; + boltOns[i].shadowPlane = shadowPlane; + + boltOns[i].hModel = ci->boltonTags[i].tagModel; + + //Get the necessary data for which mesh to bolt to + switch ( ci->boltonTags[i].modelBase ){ + case BOLTON_HEAD: + target = &head; + targetModel = ci->headModel; + break; + case BOLTON_TORSO: + target = &torso; + targetModel = ci->torsoModel; + break; + case BOLTON_LEGS: + default: + target = &legs; + targetModel = ci->legsModel; + break; + } + CG_PositionEntityOnTag( &boltOns[i], target, targetModel, ci->boltonTags[i].tagName ); + + if ( VectorCompare( boltOns[i].origin, target->origin ) ) { + break; + } + CG_AddRefEntityWithPowerups( &boltOns[i], cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, borg ); + } + } + + //============================================================================= + // teh much talked about rank system :) + // + // + + //First, check class to determine whether we'll have default or actual rank models displayed + if ( !cgs.classData[ci->pClass].showRanks || !ci->hasRanks /*ci->pClass == PC_NOCLASS || ci->pClass == PC_ALIEN || ci->pClass == PC_N00B*/ ) { + if ( cgs.defaultRankData.rankModelData.boltModelPath[0] ) { + rankModelData = &cgs.defaultRankData.rankModelData; + rankDataValidated = qtrue; + } + } + else { //Q_log2 + if ( cgs.ranksData[cg.scores[clientNum].score].rankModelData.boltModelPath[0] ) { + + //Ah... found the error with the ranks displaying wrong. Turns out cg.scores[val] is sorted by score ranking, not clientNum + //Gotta manually find ourselves in the list. + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( cg.scores[i].client == clientNum ) { + break; + } + } + + //rankModelData = &cgs.ranksData[Q_log2( cg.scores[clientNum].score )].rankModelData; + rankModelData = &cgs.ranksData[cg.scores[i].score].rankModelData; + if ( rankModelData ) + rankDataValidated = qtrue; + } + } + + if ( !rankDataValidated || cg_noDynamicRanks.integer != 0 ) { + return; + } + + //Now check to see if we actually got any data + //I've purposely created this deferred kind of rank loading + //system to try and minimize the amount of shader slots this thing will use at one time. :P + //No point in loading it until we use it + if ( rankModelData->boltModelPath[0] ) { + //if we didn't try to cache it, try now + if ( !rankModelData->boltModel && !rankModelData->triedToLoad ) { + rankModelData->boltModel = trap_R_RegisterModel( rankModelData->boltModelPath ); + + if ( !rankModelData->boltModel ) { + Com_Printf( S_COLOR_RED "Unable to load model file: %s\n", rankModelData->boltModelPath ); + } + + if ( rankModelData->boltShaderPath[0] ) { + //rankModelData->boltShader = trap_R_RegisterSkin( rankModelData->boltShaderPath ); + rankModelData->boltShader = trap_R_RegisterShader( rankModelData->boltShaderPath ); + + if ( !rankModelData->boltShader ) { + Com_Printf( S_COLOR_RED "Unable to load skin: %s\n", rankModelData->boltShaderPath ); + } + } + + rankModelData->triedToLoad = qtrue; + } + } + + //Now for the actual renderisation of the model + + if ( rankModelData->boltModel ) { + refEntity_t rankPip; + + memset( &rankPip, 0, sizeof( rankPip ) ); + VectorCopy( cent->lerpOrigin, rankPip.lightingOrigin ); + rankPip.shadowPlane = shadowPlane; + rankPip.renderfx = renderfx; + + rankPip.hModel = rankModelData->boltModel; + if ( rankModelData->boltShader ) { + //rankPip.customSkin = rankModelData->boltShader; + rankPip.customShader = rankModelData->boltShader; + } + + CG_PositionEntityOnTag( &rankPip, &head, head.hModel, "tag_pip" ); + + if ( VectorCompare( rankPip.origin, head.origin ) ) { + return; + } + + CG_AddRefEntityWithPowerups( &rankPip, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, borg ); + + //for admiral ranks, save the data and re-use, but offset origin+angles + if ( rankModelData->admiralRank ) { + /*refEntity_t rankPip1; + + memset( &rankPip1, 0, sizeof( rankPip1 ) ); + VectorCopy( cent->lerpOrigin, rankPip1.lightingOrigin ); + rankPip.shadowPlane = shadowPlane; + rankPip.renderfx = renderfx; + + rankPip1.hModel = rankModelData->boltModel; + if ( rankModelData->boltShader ) { + rankPip1.customSkin = rankModelData->boltShader; + }*/ + + //CG_PositionEntityOnTag( &rankPip1, &head, head.hModel, "tag_pip1" ); + CG_PositionEntityOnTag( &rankPip, &head, head.hModel, "tag_pip1" ); + + if ( VectorCompare( rankPip.origin, head.origin ) ) { + return; + } + + CG_AddRefEntityWithPowerups( &rankPip, cent->currentState.powerups, cent->currentState.eFlags, ¢->beamData, cent->cloakTime, cent->decloakTime, borg ); + + } + } + + //TiM - try out the procedural rank quad maker + //*sob* no normals = doesn't work :'( + //{ + // refEntity_t pip; + // polyVert_t verts[4]; + + // //use this to quickly get the tag porsition + orientation + // CG_PositionEntityOnTag( &pip, &head, head.hModel, "tag_pip" ); + // + // if ( VectorCompare( pip.origin, vec3_origin ) || cent->beamData.beamAlpha < 0.5f ) + // return; + + // //lower left + // verts[0].modulate[0] = 0xff; + // verts[0].modulate[1] = 0xff; + // verts[0].modulate[2] = 0xff; + // verts[0].modulate[3] = 0xff; + // verts[0].st[0] = 0; + // verts[0].st[1] = 1; + // + // //upper left + // verts[1].modulate[0] = 0xff; + // verts[1].modulate[1] = 0xff; + // verts[1].modulate[2] = 0xff; + // verts[1].modulate[3] = 0xff; + // verts[1].st[0] = 0; + // verts[1].st[1] = 0; + + // //upper right + // verts[2].modulate[0] = 0xff; + // verts[2].modulate[1] = 0xff; + // verts[2].modulate[2] = 0xff; + // verts[2].modulate[3] = 0xff; + // verts[2].st[0] = 1; + // verts[2].st[1] = 0; + + // //lower right + // verts[3].modulate[0] = 0xff; + // verts[3].modulate[1] = 0xff; + // verts[3].modulate[2] = 0xff; + // verts[3].modulate[3] = 0xff; + // verts[3].st[0] = 1; + // verts[3].st[1] = 1; + + // //offset each vert per axis + // //lower left + // VectorMA( pip.origin, -10, pip.axis[1], verts[0].xyz ); //shift the vert right + // VectorMA( verts[0].xyz, -5, pip.axis[2], verts[0].xyz ); + + // //upper left + // VectorMA( pip.origin, -10, pip.axis[1], verts[1].xyz ); //shift the vert right + // VectorMA( verts[1].xyz, 5, pip.axis[2], verts[1].xyz ); + + // //upper right + // VectorMA( pip.origin, 10, pip.axis[1], verts[2].xyz ); //shift the vert right + // VectorMA( verts[2].xyz, 5, pip.axis[2], verts[2].xyz ); + + // //lower right + // VectorMA( pip.origin, 10, pip.axis[1], verts[3].xyz ); //shift the vert right + // VectorMA( verts[3].xyz, -5, pip.axis[2], verts[3].xyz ); + + // trap_R_AddPolyToScene( rankModelData->boltShader, 4, verts ); + //} +} + +//===================================================================== + +/* +=============== +CG_ResetPlayerEntity + +A player just came into view or teleported, so reset all animation info +=============== +*/ +void CG_ResetPlayerEntity( centity_t *cent ) { + qboolean isDecoy=qfalse; + + isDecoy = (cent->currentState.eFlags & EF_ITEMPLACEHOLDER); + + cent->errorTime = -99999; // guarantee no error decay added + cent->extrapolated = qfalse; + + if ( !isDecoy ) { + CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.legs, cent->currentState.legsAnim ); + CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.torso, cent->currentState.torsoAnim ); + } + else { + CG_ClearLerpFrame( &cgs.decoyInfo[ cent->currentState.eventParm ], ¢->pe.legs, cent->currentState.legsAnim ); + CG_ClearLerpFrame( &cgs.decoyInfo[ cent->currentState.eventParm ], ¢->pe.torso, cent->currentState.torsoAnim ); + } + + 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 ); + + //TiM - Only do this if not emoting. Otherwise, if we go away, then come back, clamped players face different directions O_O + //ignore this for decoys too. they're static, so this won't affect them anyway + + if ( !isDecoy && ( ( cent->currentState.eFlags & EF_CLAMP_BODY) || ( cent->currentState.eFlags & EF_CLAMP_ALL) ) ) { + //blah + } + else { + 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; + } + + if ( cg_debugPosition.integer ) { + CG_Printf("%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle ); + } +} + diff --git a/cgame/cg_playerstate.c b/cgame/cg_playerstate.c new file mode 100644 index 0000000..9184585 --- /dev/null +++ b/cgame/cg_playerstate.c @@ -0,0 +1,500 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_playerstate.c -- this file acts on changes in a new playerState_t +// With normal play, this will be done after local prediction, but when +// following another player or playing back a demo, it will be checked +// when the snapshot transitions like all the other entities + +#include "cg_local.h" + +/* +============== +CG_CheckAmmo + +If the ammo has gone low enough to generate the warning, play a sound +============== +*/ +void CG_CheckAmmo( void ) { + int i; + int total; + int previous; + int weapons; + + if ( cg.lowAmmoWarning > 2 ) + {//a timed message, draws for a specific amount of time + if ( cg.lowAmmoWarning > cg.frametime ) + { + cg.lowAmmoWarning -= cg.frametime; + } + else + { + cg.lowAmmoWarning = 0; + } + return; + } + // see about how many seconds of ammo we have remaining + weapons = cg.snap->ps.stats[ STAT_WEAPONS ]; + total = 0; + + //TiM + //for ( i = WP_PHASER ; i < WP_NUM_WEAPONS ; i++ ) { + for ( i = WP_NULL_HAND ; i < WP_NUM_WEAPONS ; i++ ) { + if ( ! ( weapons & ( 1 << i ) ) ) { + continue; + } + switch ( i ) { + case WP_DISRUPTOR: + case WP_GRENADE_LAUNCHER: + case WP_NULL_HAND: + case WP_COMPRESSION_RIFLE: + total += cg.snap->ps.ammo[i] * 1000; + break; + default: + total += cg.snap->ps.ammo[i] * 200; + break; + } + if ( total >= 5000 ) { + cg.lowAmmoWarning = 0; + return; + } + } + + previous = cg.lowAmmoWarning; + + if ( total == 0 ) { + cg.lowAmmoWarning = 2; + } else { + cg.lowAmmoWarning = 1; + } + + // play a sound on transitions + // RPG-X | Phenix | 13/02/2005 + /*if ( cg.lowAmmoWarning != previous ) { + trap_S_StartLocalSound( cgs.media.noAmmoSound, CHAN_LOCAL_SOUND ); + }*/ +} + +/* +============== +CG_DamageFeedback +============== +*/ +void CG_DamageFeedback( int yawByte, int pitchByte, int damage, int shielddamage ) { + float left, front, up; + float kick; + int health; + float scale; + vec3_t dir; + vec3_t angles; + float dist; + float yaw, pitch; + + // show the attacking player's head and name in corner + cg.attackerTime = cg.time; + + // the lower on health you are, the greater the view kick will be + health = cg.snap->ps.stats[STAT_HEALTH]; + if ( health < 40 ) { + scale = 1; + } else { + scale = 40.0 / health; + } + + kick = (damage + shielddamage*0.5) * scale; + + if (kick < 5) + kick = 5; + if (kick > 10) + kick = 10; + + // if yaw and pitch are both 255, make the damage always centered (falling, etc) + if ( yawByte == 255 && pitchByte == 255 ) { + cg.damageX = 0; + cg.damageY = 0; + cg.v_dmg_roll = 0; + cg.v_dmg_pitch = -kick; + } else { + // positional + pitch = pitchByte / 255.0 * 360; + yaw = yawByte / 255.0 * 360; + + angles[PITCH] = pitch; + angles[YAW] = yaw; + angles[ROLL] = 0; + + AngleVectors( angles, dir, NULL, NULL ); + VectorSubtract( vec3_origin, dir, dir ); + + front = DotProduct (dir, cg.refdef.viewaxis[0] ); + left = DotProduct (dir, cg.refdef.viewaxis[1] ); + up = DotProduct (dir, cg.refdef.viewaxis[2] ); + + dir[0] = front; + dir[1] = left; + dir[2] = 0; + dist = VectorLength( dir ); + if ( dist < 0.1 ) { + dist = 0.1; + } + + cg.v_dmg_roll = kick * left; + + cg.v_dmg_pitch = -kick * front; + + if ( front <= 0.1 ) { + front = 0.1; + } + cg.damageX = -left / front; + cg.damageY = up / dist; + } + + // clamp the position + if ( cg.damageX > 1.0 ) { + cg.damageX = 1.0; + } + if ( cg.damageX < - 1.0 ) { + cg.damageX = -1.0; + } + + if ( cg.damageY > 1.0 ) { + cg.damageY = 1.0; + } + if ( cg.damageY < - 1.0 ) { + cg.damageY = -1.0; + } + + cg.damageValue = damage * scale; + if (cg.damageValue > 10) + { + cg.damageValue = 1.0; + } + else + { + cg.damageValue *= 0.1; + } + cg.damageShieldValue = shielddamage; + if (cg.damageShieldValue > 10) + { + cg.damageShieldValue = 1.0; + } + else + { + cg.damageShieldValue *= 0.1; + } + + cg.v_dmg_time = cg.time + DAMAGE_TIME; + cg.damageTime = cg.snap->serverTime; +} + + + + +/* +================ +CG_Respawn + +A respawn happened this snapshot +================ +*/ +void CG_Respawn( void ) { + // no error decay on player movement + cg.thisFrameTeleport = qtrue; + + // display weapons available + cg.weaponSelectTime = cg.time; + + // select the weapon the server says we are using + cg.weaponSelect = cg.snap->ps.weapon; +} + + +/* +============== +CG_CheckPlayerstateEvents +============== +*/ +void CG_CheckPlayerstateEvents( playerState_t *ps, playerState_t *ops ) { + int i; + int event; + centity_t *cent; + + if ( ps->externalEvent && ps->externalEvent != ops->externalEvent ) { + cent = &cg_entities[ ps->clientNum ]; + cent->currentState.event = ps->externalEvent; + cent->currentState.eventParm = ps->externalEventParm; + CG_EntityEvent( cent, cent->lerpOrigin ); + } + + cent = &cg.predictedPlayerEntity; // cg_entities[ ps->clientNum ]; + // go through the predictable events buffer + for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) { + // if we have a new predictable event + if ( i >= ops->eventSequence + // or the server told us to play another event instead of a predicted event we already issued + // or something the server told us changed our prediction causing a different event + || (i > ops->eventSequence - MAX_PS_EVENTS && ps->events[i & (MAX_PS_EVENTS-1)] != ops->events[i & (MAX_PS_EVENTS-1)]) ) { + + event = ps->events[ i & (MAX_PS_EVENTS-1) ]; + cent->currentState.event = event; + cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ]; + CG_EntityEvent( cent, cent->lerpOrigin ); + +// cg.predictableEvents[ i & (MAX_PREDICTED_EVENTS-1) ] = event; + +// cg.eventSequence++; + } + } +} + +/* +================== +CG_CheckLocalSounds +================== +*/ +void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) +{ +// int highScore; + + // The most important thing to know is if you are doing damage. + //RPG-X - TiM + /*if ( ps->persistant[PERS_HITS] > ops->persistant[PERS_HITS] ) + { + int diffhit, diffshields; + + diffhit = ps->persistant[PERS_HITS] - ops->persistant[PERS_HITS]; + diffshields = ps->persistant[PERS_SHIELDS] - ops->persistant[PERS_SHIELDS]; + if (diffshields > diffhit/2) + { // We also hit shields along the way, so consider them "pierced". + trap_S_StartLocalSound( cgs.media.shieldPierceSound, CHAN_LOCAL_SOUND ); + } + else + { // Shields didn't really stand in our way. + trap_S_StartLocalSound( cgs.media.hitSound, CHAN_LOCAL_SOUND ); + } + } + // The second most important thing to worry about is whether you hurt a friend. + else if ( ps->persistant[PERS_HITS] < ops->persistant[PERS_HITS] ) + { + trap_S_StartLocalSound( cgs.media.hitTeamSound, CHAN_LOCAL_SOUND ); + } + // Finally if all this damage bounced off the shields, indicate this. + else if (ps->persistant[PERS_SHIELDS] > ops->persistant[PERS_SHIELDS]) + { + // hit shields and the damage didn't go through + trap_S_StartLocalSound( cgs.media.shieldHitSound, CHAN_LOCAL_SOUND ); + }*/ + + // health changes of more than -1 should make pain sounds + if ( ps->stats[STAT_HEALTH] < ops->stats[STAT_HEALTH] - 1 ) { + if ( ps->stats[STAT_HEALTH] > 0 ) { + CG_PainEvent( &cg.predictedPlayerEntity, ps->stats[STAT_HEALTH] ); + } + } + + + // if we are going into the intermission, don't start any voices + if ( cg.intermissionStarted ) { + return; + } + + // reward sounds + //RPG-X: RedTechie - No reward or frag limit sounds + /*if ( ps->persistant[PERS_REWARD_COUNT] > ops->persistant[PERS_REWARD_COUNT] ) { + switch ( ps->persistant[PERS_REWARD] ) { + case REWARD_IMPRESSIVE: + trap_S_StartLocalSound( cgs.media.rewardImpressiveSound, CHAN_ANNOUNCER ); + cg.rewardTime = cg.time; + cg.rewardShader = cgs.media.medalImpressive; + cg.rewardCount = ps->persistant[PERS_IMPRESSIVE_COUNT]; + break; + case REWARD_EXCELLENT: + trap_S_StartLocalSound( cgs.media.rewardExcellentSound, CHAN_ANNOUNCER ); + cg.rewardTime = cg.time; + cg.rewardShader = cgs.media.medalExcellent; + cg.rewardCount = ps->persistant[PERS_EXCELLENT_COUNT]; + break; + case REWARD_DENIED: + trap_S_StartLocalSound( cgs.media.rewardDeniedSound, CHAN_ANNOUNCER ); + break; + case REWARD_FIRST_STRIKE: + trap_S_StartLocalSound( cgs.media.rewardFirstStrikeSound, CHAN_ANNOUNCER); + cg.rewardTime = cg.time; + cg.rewardShader = cgs.media.medalFirstStrike; + cg.rewardCount = 1; + break; + case REWARD_STREAK: + // Play a different sound depending on how long the streak is. + cg.rewardTime = cg.time; + cg.rewardCount = 1; + if (ps->persistant[PERS_STREAK_COUNT] >= STREAK_CHAMPION) + { + trap_S_StartLocalSound( cgs.media.rewardChampionSound, CHAN_ANNOUNCER); + cg.rewardShader = cgs.media.medalChampion; + } + else if (ps->persistant[PERS_STREAK_COUNT] >= STREAK_MASTER) + { + trap_S_StartLocalSound( cgs.media.rewardMasterSound, CHAN_ANNOUNCER); + cg.rewardShader = cgs.media.medalMaster; + } + else if (ps->persistant[PERS_STREAK_COUNT] >= STREAK_EXPERT) + { + trap_S_StartLocalSound( cgs.media.rewardExpertSound, CHAN_ANNOUNCER); + cg.rewardShader = cgs.media.medalExpert; + } + else if (ps->persistant[PERS_STREAK_COUNT] >= STREAK_ACE) + { + trap_S_StartLocalSound( cgs.media.rewardAceSound, CHAN_ANNOUNCER); + cg.rewardShader = cgs.media.medalAce; + } + break; + default: + CG_Error( "Bad reward_t" ); + } + } else { + // lead changes (only if no reward) + if ( !cg.warmup && !(cg.predictedPlayerState.introTime > cg.time) ) + { + // never play lead changes during warmup or holo doors + if ( ps->persistant[PERS_RANK] != ops->persistant[PERS_RANK] ) { + if ( cgs.gametype >= GT_TEAM ) { + if ( ps->persistant[PERS_RANK] == 2 ) { + trap_S_StartLocalSound( cgs.media.teamsTiedSound, CHAN_ANNOUNCER ); + } else if ( ps->persistant[PERS_RANK] == 0 ) { + trap_S_StartLocalSound( cgs.media.redLeadsSound, CHAN_ANNOUNCER ); + } else if ( ps->persistant[PERS_RANK] == 1 ) { + trap_S_StartLocalSound( cgs.media.blueLeadsSound, CHAN_ANNOUNCER ); + } + } else { + if ( ps->persistant[PERS_RANK] == 0 ) { + trap_S_StartLocalSound( cgs.media.takenLeadSound, CHAN_ANNOUNCER ); + } else if ( ps->persistant[PERS_RANK] == RANK_TIED_FLAG ) { + trap_S_StartLocalSound( cgs.media.tiedLeadSound, CHAN_ANNOUNCER ); + } else if ( ( ops->persistant[PERS_RANK] & ~RANK_TIED_FLAG ) == 0 ) { + trap_S_StartLocalSound( cgs.media.lostLeadSound, CHAN_ANNOUNCER ); + } + } + } + } + } + + // timelimit warnings + if ( cgs.timelimit > 0 ) { + int msec; + + msec = cg.time - cgs.levelStartTime; + + if ( cgs.timelimit > 5 && !( cg.timelimitWarnings & 1 ) && msec > (cgs.timelimit - 5) * 60 * 1000 ) { + cg.timelimitWarnings |= 1; + trap_S_StartLocalSound( cgs.media.fiveMinuteSound, CHAN_ANNOUNCER ); + } + if ( !( cg.timelimitWarnings & 2 ) && msec > (cgs.timelimit - 1) * 60 * 1000 ) { + cg.timelimitWarnings |= 2; + trap_S_StartLocalSound( cgs.media.oneMinuteSound, CHAN_ANNOUNCER ); + } + if ( !( cg.timelimitWarnings & 4 ) && msec > ( cgs.timelimit * 60 + 2 ) * 1000 ) { + cg.timelimitWarnings |= 4; + trap_S_StartLocalSound( cgs.media.suddenDeathSound, CHAN_ANNOUNCER ); + } + } + + // fraglimit warnings + if ( cgs.fraglimit > 0 && cgs.gametype != GT_CTF ) { + highScore = cgs.scores1; + if ( cgs.fraglimit > 3 && !( cg.fraglimitWarnings & 1 ) && highScore == (cgs.fraglimit - 3) ) { + cg.fraglimitWarnings |= 1; + trap_S_StartLocalSound( cgs.media.threeFragSound, CHAN_ANNOUNCER ); + } + if ( cgs.fraglimit > 2 && !( cg.fraglimitWarnings & 2 ) && highScore == (cgs.fraglimit - 2) ) { + cg.fraglimitWarnings |= 2; + trap_S_StartLocalSound( cgs.media.twoFragSound, CHAN_ANNOUNCER ); + } + if ( !( cg.fraglimitWarnings & 4 ) && highScore == (cgs.fraglimit - 1) ) { + cg.fraglimitWarnings |= 4; + trap_S_StartLocalSound( cgs.media.oneFragSound, CHAN_ANNOUNCER ); + } + }*/ +} + + +void CG_CheckDamageDealt(playerState_t *ps, playerState_t *ops) +{ + static int damagetime; + static int damageamount; + + if (cg_reportDamage.integer) + { + if (ps->persistant[PERS_HITS] > ops->persistant[PERS_HITS]) + { // We did some damage this frame. + if (damagetime+1000 < cg.time) + { // Start a new tally. + damageamount = ps->persistant[PERS_HITS] - ops->persistant[PERS_HITS]; + damagetime = cg.time; + } + else + { // Add to a tally that's already here. + damageamount += ps->persistant[PERS_HITS] - ops->persistant[PERS_HITS]; + } + } + + // Report the sum of damage done this second. + if (damageamount > 0 && (damagetime+1000 <= cg.time)) + { + Com_Printf("Damage this second: %d\n", damageamount); + damageamount = 0; + } + } +} + + + +/* +=============== +CG_TransitionPlayerState + +=============== +*/ +void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ) { + // check for changing follow mode + if ( ps->clientNum != ops->clientNum ) { + cg.thisFrameTeleport = qtrue; + // make sure we don't get any unwanted transition effects + *ops = *ps; + } + + // damage events (player is getting wounded) + if ( ps->damageEvent != ops->damageEvent && (ps->damageCount || ps->damageShieldCount)) { + CG_DamageFeedback( ps->damageYaw, ps->damagePitch, ps->damageCount, ps->damageShieldCount); + } + + // respawning + if ( ps->persistant[PERS_SPAWN_COUNT] != ops->persistant[PERS_SPAWN_COUNT] ) { + CG_Respawn(); + } + +/* if ( cg.mapRestart ) { //q3 update -not tested yet + CG_Respawn(); + cg.mapRestart = qfalse; + } +*/ + if ( cg.snap->ps.pm_type != PM_INTERMISSION + && ps->persistant[PERS_TEAM] != TEAM_SPECTATOR /*&& !(ps->eFlags&EF_ELIMINATED)*/) { + CG_CheckLocalSounds( ps, ops ); + } + + // check for going low on ammo + CG_CheckAmmo(); + + // run events + CG_CheckPlayerstateEvents( ps, ops ); + + // smooth the ducking viewheight change + if ( ps->viewheight != ops->viewheight ) { + cg.duckChange = ps->viewheight - ops->viewheight; + cg.duckTime = cg.time; + } + +#ifdef _DEBUG + CG_CheckDamageDealt(ps, ops); +#endif //_DEBUG +} + diff --git a/cgame/cg_predict.c b/cgame/cg_predict.c new file mode 100644 index 0000000..214b261 --- /dev/null +++ b/cgame/cg_predict.c @@ -0,0 +1,693 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_predict.c -- this file generates cg.predictedPlayerState by either +// interpolating between snapshots from the server or locally predicting +// ahead the client's movement. +// It also handles local physics interaction, like fragments bouncing off walls + +#include "cg_local.h" + +static pmove_t cg_pmove; + +static int cg_numSolidEntities; +static centity_t *cg_solidEntities[MAX_ENTITIES_IN_SNAPSHOT]; +static int cg_numTriggerEntities; +static centity_t *cg_triggerEntities[MAX_ENTITIES_IN_SNAPSHOT]; + +/* +==================== +CG_BuildSolidList + +When a new cg.snap has been set, this function builds a sublist +of the entities that are actually solid, to make for more +efficient collision detection +==================== +*/ +void CG_BuildSolidList( void ) { + int i; + centity_t *cent; + snapshot_t *snap; + entityState_t *ent; + + cg_numSolidEntities = 0; + cg_numTriggerEntities = 0; + + if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) { + snap = cg.nextSnap; + } else { + snap = cg.snap; + } + + for ( i = 0 ; i < snap->numEntities ; i++ ) { + cent = &cg_entities[ snap->entities[ i ].number ]; + ent = ¢->currentState; + + 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; + } + } +} + +/* +==================== +CG_ClipMoveToEntities + +==================== +*/ +#define SHIELD_HALFTHICKNESS 4 // should correspond with the #define in g_active.c + +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 if (ent->eFlags & EF_SHIELD_BOX_X) + { // "specially" encoded bbox for x-axis aligned shield + + //CG_Printf( S_COLOR_RED "Mins[ %d %d %d ] Maxs[ %d %d %d ]\n", mins[0], mins[1], mins[2], maxs[0], maxs[1], maxs[2] ); + + //this is a bit of a hack. Only deny entry to elements + //that do not specifiy collision boundaries. + //This will mean things like players won't be affected + //(there'll be a slight jerk as the server boots them back) + //but any FX like phaser beams will be. + if ( !mins || !VectorCompare( mins, vec3_origin ) || !VectorCompare( maxs, vec3_origin ) ) + continue; + + /*x = (ent->solid & 255); // i on server side + zd = ((ent->solid>>8) & 255); // j on server side + zu = ((ent->solid>>16) & 255); // k on server side*/ + + x = (ent->time2 & 1023); // i on server side + zd = ((ent->time2>>10) & 1023); // j on server side + zu = ((ent->time2>>20) & 1023); // k on server side + + bmins[0] = -x; //-zd + bmaxs[0] = zd; //x + bmins[1] = -SHIELD_HALFTHICKNESS; + bmaxs[1] = SHIELD_HALFTHICKNESS; + bmins[2] = 0; + bmaxs[2] = zu; + + cmodel = trap_CM_TempBoxModel( bmins, bmaxs ); + VectorCopy( vec3_origin, angles ); + VectorCopy( cent->lerpOrigin, origin ); + + //CG_Printf( S_COLOR_RED "X Aligned! Bmins = [ %f %f %f ],\nBMaxs = [%f %f %f]\n", bmins[0], bmins[1], bmins[2], bmaxs[0],bmaxs[1],bmaxs[2] ); + } + else if (ent->eFlags & EF_SHIELD_BOX_Y) + { // "specially" encoded bbox for y-axis aligned shield + /*x = (ent->solid & 255); // i on server side + zd = ((ent->solid>>8) & 255); // j on server side + zu = ((ent->solid>>16) & 255); // k on server side*/ + + if ( !VectorCompare( mins, vec3_origin ) || !VectorCompare( maxs, vec3_origin ) ) + continue; + + x = (ent->time2 & 1023); // i on server side + zd = ((ent->time2>>10) & 1023); // j on server side + zu = ((ent->time2>>20) & 1023); // k on server side + + bmins[1] = -x; + bmaxs[1] = zd; + bmins[0] = -SHIELD_HALFTHICKNESS; + bmaxs[0] = SHIELD_HALFTHICKNESS; + bmins[2] = 0; + bmaxs[2] = zu; + + cmodel = trap_CM_TempBoxModel( bmins, bmaxs ); + VectorCopy( vec3_origin, angles ); + VectorCopy( cent->lerpOrigin, origin ); + } + else + { + // encoded bbox + x = (ent->solid & 255); // i on server side + zd = ((ent->solid>>8) & 255); // j on server side + zu = ((ent->solid>>16) & 255) - 32; // k on server side + + 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_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 || next->serverTime <= prev->serverTime ) { + return; + } + + f = (float)( cg.time - prev->serverTime ) / ( next->serverTime - prev->serverTime ); + + i = next->ps.bobCycle; + if ( i < prev->ps.bobCycle ) { + i += 256; // handle wraparound + } + out->bobCycle = prev->ps.bobCycle + f * ( i - prev->ps.bobCycle ); + + for ( i = 0 ; i < 3 ; i++ ) { + out->origin[i] = prev->ps.origin[i] + f * (next->ps.origin[i] - prev->ps.origin[i] ); + if ( !grabAngles ) { + out->viewangles[i] = LerpAngle( + prev->ps.viewangles[i], next->ps.viewangles[i], f ); + } + out->velocity[i] = prev->ps.velocity[i] + + f * (next->ps.velocity[i] - prev->ps.velocity[i] ); + } + +} + +/* +=================== +CG_TouchItem +=================== +*/ +static void CG_TouchItem( centity_t *cent ) { + gitem_t *item; + + //RPG-X | Marcin | 03/12/2008 + //this can't be predicted because as don't know whether USE was pressed... + if (qtrue) { + return; + } + + if ( !cg_predictItems.integer ) { + return; + } + + // never pick an item up twice in a prediction + if ( cent->miscTime == cg.time ) { + return; + } + + if ( !BG_PlayerTouchesItem( &cg.predictedPlayerState, ¢->currentState, cg.time ) ) { + return; + } + + // RPG-X: Marcin: Can't predict this any more sorry - 30/12/2008 + if ( 0 ) /* ( !BG_CanItemBeGrabbed( ¢->currentState, &cg.predictedPlayerState ) ) */ { + return; // can't hold it + } + + item = &bg_itemlist[ cent->currentState.modelindex ]; + + // Special case for flags. + // We don't predict touching our own flag + if (item->giType == IT_TEAM) + { // NOTE: This code used to JUST check giTag. The problem is that the giTag for PW_REDFLAG + // is the same as WP_QUANTUM_BURST. The giTag should be a SUBCHECK after giType. + /*if (cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_RED && + item->giTag == PW_REDFLAG) + return;*/ + if (cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_BLUE && + item->giTag == PW_BORG_ADAPT) + return; + } + + if (!(cent->currentState.eFlags & (EF_ITEMPLACEHOLDER | EF_NODRAW))) + { + // grab it + BG_AddPredictableEventToPlayerstate( EV_ITEM_PICKUP, cent->currentState.modelindex , &cg.predictedPlayerState); + + // Draw the techy-itemplaceholder for weapons and powerups, not ammo, etc. + if (item->giType == IT_WEAPON || item->giType == IT_POWERUP) + { // draw it "gridded out" + cent->currentState.eFlags |= EF_ITEMPLACEHOLDER; + } + else + { // remove it from the frame so it won't be drawn + cent->currentState.eFlags |= EF_NODRAW; + // kef -- special client-only flag to prevent double pickup sounds + //cent->currentState.eFlags |= EF_CLIENT_NODRAW; + } + // 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; + } + } + } + + // don't touch it again this prediction + cent->miscTime = cg.time; +} + + +/* +========================= +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; + continue; + } else { + float s; + vec3_t dir; + + // we hit this push trigger + if ( spectator ) { + continue; + } + + // flying characters don't hit bounce pads + if ( cg.predictedPlayerState.powerups[PW_FLIGHT] ) { + continue; + } + + // if we are already flying along the bounce direction, don't play sound again + VectorNormalize2( ent->origin2, dir ); + s = DotProduct( cg.predictedPlayerState.velocity, dir ); + if ( s < 500 ) { + // don't play the event sound again if we are in a fat trigger + BG_AddPredictableEventToPlayerstate( EV_JUMP_PAD, 0, &cg.predictedPlayerState ); + } + VectorCopy( ent->origin2, cg.predictedPlayerState.velocity ); + } + } +} + + +/* +================= +CG_PredictPlayerState + +Generates cg.predictedPlayerState for the current cg.time +cg.predictedPlayerState is guaranteed to be valid after exiting. + +For demo playback, this will be an interpolation between two valid +playerState_t. + +For normal gameplay, it will be the result of predicted usercmd_t on +top of the most recent playerState_t received from the server. + +Each new snapshot will usually have one or more new usercmd over the last, +but we simulate all unacknowledged commands each time, not just the new ones. +This means that on an internet connection, quite a few pmoves may be issued +each frame. + +OPTIMIZE: don't re-simulate unless the newly arrived snapshot playerState_t +differs from the predicted one. Would require saving all intermediate +playerState_t during prediction. + +We detect prediction errors and allow them to be decayed off over several frames +to ease the jerk. +================= +*/ +void CG_PredictPlayerState( void ) { + int cmdNum, current; + playerState_t oldPlayerState; + qboolean moved; + usercmd_t oldestCmd; + usercmd_t latestCmd; + char temp_string[200]; + + cg.hyperspace = qfalse; // will be set if touching a trigger_teleport + + // if this is the first frame we must guarantee + // predictedPlayerState is valid even if there is some + // other error condition + if ( !cg.validPPS ) { + cg.validPPS = qtrue; + cg.predictedPlayerState = cg.snap->ps; + // if we need to, we should check our model cvar and update it with the right value from the userinfo strings + // since it may have been modified by the server + Com_sprintf(temp_string, sizeof(temp_string), "%s/%s/%s", cgs.clientinfo[cg.predictedPlayerState.clientNum].charName, cgs.clientinfo[cg.predictedPlayerState.clientNum].modelName, + cgs.clientinfo[cg.predictedPlayerState.clientNum].skinName); + updateSkin(cg.predictedPlayerState.clientNum, temp_string); + } + + + // demo playback just copies the moves + if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) ) { + CG_InterpolatePlayerState( qfalse ); + return; + } + + // non-predicting local movement will grab the latest angles + if ( cg_nopredict.integer || cg_synchronousClients.integer ) { + CG_InterpolatePlayerState( qtrue ); + return; + } + + // prepare for pmove + cg_pmove.ps = &cg.predictedPlayerState; + cg_pmove.trace = CG_Trace; + cg_pmove.pointcontents = CG_PointContents; + + cg_pmove.admin = cgs.clientinfo[cg.snap->ps.clientNum].isAdmin; + cg_pmove.medic = cgs.classData[cgs.clientinfo[cg.snap->ps.clientNum].pClass].isMedic; + cg_pmove.borg = cgs.classData[cgs.clientinfo[cg.snap->ps.clientNum].pClass].isBorg; + + 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.snap->ps.eFlags&EF_ELIMINATED)*/ ) { + cg_pmove.tracemask &= ~CONTENTS_BODY; // spectators can fly through bodies + } + cg_pmove.noFootsteps = ( cgs.dmflags & DF_NO_FOOTSTEPS ) > 0; + cg_pmove.pModDisintegration = cgs.pModDisintegration > 0; + + // save the state before the pmove so we can detect transitions + oldPlayerState = cg.predictedPlayerState; + + current = trap_GetCurrentCmdNumber(); + + // if we don't have the commands right after the snapshot, we + // can't accurately predict a current position, so just freeze at + // the last good position we had + cmdNum = current - CMD_BACKUP + 1; + trap_GetUserCmd( cmdNum, &oldestCmd ); + if ( oldestCmd.serverTime > cg.snap->ps.commandTime + && oldestCmd.serverTime < cg.time ) { // special check for map_restart + if ( cg_showmiss.integer ) { + CG_Printf ("exceeded PACKET_BACKUP on commands\n"); + } + return; + } + + // get the latest command so we can know which commands are from previous map_restarts + trap_GetUserCmd( current, &latestCmd ); + + // get the most recent information we have, even if + // the server time is beyond our current cg.time, + // because predicted player positions are going to + // be ahead of everything else anyway + if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) { + cg.predictedPlayerState = cg.nextSnap->ps; + cg.physicsTime = cg.nextSnap->serverTime; + } else { + cg.predictedPlayerState = cg.snap->ps; + cg.physicsTime = cg.snap->serverTime; + } + + // run cmds + moved = qfalse; + for ( cmdNum = current - CMD_BACKUP + 1 ; cmdNum <= current ; cmdNum++ ) { + // get the command + trap_GetUserCmd( cmdNum, &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; + } + + /*if (cg.predictedPlayerState.introTime > cg.time) //what's this for? TiM: I think it's for the holoIntro... + { + cg_pmove.cmd.buttons = 0; + cg_pmove.cmd.weapon = 0; +// cg_pmove.cmd.angles[0] = cg_pmove.cmd.angles[1] = cg_pmove.cmd.angles[2] = 0; + cg_pmove.cmd.forwardmove = cg_pmove.cmd.rightmove = cg_pmove.cmd.upmove = 0; + }*/ + + // check for a prediction error from last frame + // on a lan, this will often be the exact value + // from the snapshot, but on a wan we will have + // to predict several commands to get to the point + // we want to compare + if ( cg.predictedPlayerState.commandTime == oldPlayerState.commandTime ) { + vec3_t delta; + float len; + + if ( cg.thisFrameTeleport ) { + // a teleport will not cause an error decay + VectorClear( cg.predictedError ); + if ( cg_showmiss.integer ) { + CG_Printf( "PredictionTeleport\n" ); + } + cg.thisFrameTeleport = qfalse; + } else { + vec3_t adjusted; + CG_AdjustPositionForMover( cg.predictedPlayerState.origin, + cg.predictedPlayerState.groundEntityNum, cg.physicsTime, cg.oldTime, adjusted ); + + if ( cg_showmiss.integer ) { + if (!VectorCompare( oldPlayerState.origin, adjusted )) { + CG_Printf("prediction error\n"); + } + } + VectorSubtract( oldPlayerState.origin, adjusted, delta ); + len = VectorLength( delta ); + if ( len > 0.1 ) { + if ( cg_showmiss.integer ) { + CG_Printf("Prediction miss: %f\n", len); + } + if ( cg_errorDecay.integer ) { + int t; + float f; + + t = cg.time - cg.predictedErrorTime; + f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; + if ( f < 0 ) { + f = 0; + } + if ( f > 0 && cg_showmiss.integer ) { + CG_Printf("Double prediction decay: %f\n", f); + } + VectorScale( cg.predictedError, f, cg.predictedError ); + } else { + VectorClear( cg.predictedError ); + } + VectorAdd( delta, cg.predictedError, cg.predictedError ); + cg.predictedErrorTime = cg.oldTime; + } + } + } + + Pmove (&cg_pmove); + + moved = qtrue; + + // add push trigger movement effects + CG_TouchTriggerPrediction(); + } + + if ( cg_showmiss.integer > 1 ) { + CG_Printf( "[%i : %i] ", cg_pmove.cmd.serverTime, cg.time ); + } + + if ( !moved ) { + if ( cg_showmiss.integer ) { + CG_Printf( "not moved\n" ); + } + return; + } + + // adjust for the movement of the groundentity + CG_AdjustPositionForMover( cg.predictedPlayerState.origin, + cg.predictedPlayerState.groundEntityNum, + cg.physicsTime, cg.time, cg.predictedPlayerState.origin ); + + if ( cg_showmiss.integer ) { + if (cg.predictedPlayerState.eventSequence > oldPlayerState.eventSequence + MAX_PS_EVENTS) { + CG_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) { + CG_Printf("WARNING: double event\n"); + cg.eventSequence = cg.predictedPlayerState.eventSequence; + } + } +*/ +} + + diff --git a/cgame/cg_public.h b/cgame/cg_public.h new file mode 100644 index 0000000..779e14f --- /dev/null +++ b/cgame/cg_public.h @@ -0,0 +1,183 @@ + + + + + +// Copyright (C) 1999-2000 Id Software, Inc. +// + + +// rpgxEF? +#define XTRA 1 + +#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; + + +/* +================================================================== + +functions imported from the main executable + +================================================================== +*/ + +#define CGAME_IMPORT_API_VERSION 3 + +//these must match up with cg_syscalls.asm +typedef enum { + CG_PRINT, + CG_ERROR, + CG_MILLISECONDS, + CG_CVAR_REGISTER, + CG_CVAR_UPDATE, + CG_CVAR_SET, + CG_CVAR_VARIABLESTRINGBUFFER, + CG_ARGC, + CG_ARGV, + CG_ARGS, + CG_FS_FOPENFILE, + CG_FS_READ, + CG_FS_WRITE, + CG_FS_FCLOSEFILE, + CG_SENDCONSOLECOMMAND, + CG_ADDCOMMAND, + CG_SENDCLIENTCOMMAND, + CG_UPDATESCREEN, + CG_CM_LOADMAP, + CG_CM_NUMINLINEMODELS, + CG_CM_INLINEMODEL, + CG_CM_LOADMODEL, + CG_CM_TEMPBOXMODEL, + CG_CM_POINTCONTENTS, + CG_CM_TRANSFORMEDPOINTCONTENTS, + CG_CM_BOXTRACE, + CG_CM_TRANSFORMEDBOXTRACE, + CG_CM_MARKFRAGMENTS, + CG_S_STARTSOUND, + CG_S_STARTLOCALSOUND, + CG_S_CLEARLOOPINGSOUNDS, + CG_S_ADDLOOPINGSOUND, + CG_S_UPDATEENTITYPOSITION, + CG_S_RESPATIALIZE, + CG_S_REGISTERSOUND, + CG_S_STARTBACKGROUNDTRACK, + CG_R_LOADWORLDMAP, + CG_R_REGISTERMODEL, + CG_R_REGISTERSKIN, + CG_R_REGISTERSHADER, + CG_R_CLEARSCENE, + CG_R_ADDREFENTITYTOSCENE, + CG_R_ADDPOLYTOSCENE, + CG_R_ADDLIGHTTOSCENE, + CG_R_RENDERSCENE, + CG_R_SETCOLOR, + CG_R_DRAWSTRETCHPIC, + CG_R_MODELBOUNDS, + CG_R_LERPTAG, + CG_GETGLCONFIG, + CG_GETGAMESTATE, + CG_GETCURRENTSNAPSHOTNUMBER, + CG_GETSNAPSHOT, + CG_GETSERVERCOMMAND, + CG_GETCURRENTCMDNUMBER, + CG_GETUSERCMD, + CG_SETUSERCMDVALUE, + CG_R_REGISTERSHADERNOMIP, + CG_MEMORY_REMAINING, //58 + CG_R_REGISTERSHADER3D, //59 + CG_CVAR_SET_NO_MODIFY, // 60 +//these must match up with cg_syscalls.asm - add more traps HERE! + + CG_MEMSET = 100, + CG_MEMCPY, + CG_STRNCPY, + CG_SIN, + CG_COS, + CG_ATAN2, + CG_SQRT, + CG_FLOOR, + CG_CEIL, + + CG_TESTPRINTINT, + CG_TESTPRINTFLOAT + + #ifdef XTRA + , + CG_R_REMAP_SHADER = 200, + CG_R_ADDPOLYSTOSCENE + #endif +} cgameImport_t; +//these must match up with cg_syscalls.asm + + +/* +================================================================== + +functions exported to the main executable + +================================================================== +*/ + +typedef enum { + CG_INIT, +// void CG_Init( int serverMessageNum, int serverCommandSequence ) + // called when the level loads or when the renderer is restarted + // all media should be registered at this time + // cgame will display loading status by calling SCR_Update, which + // will call CG_DrawInformation during the loading process + // reliableCommandSequence will be 0 on fresh loads, but higher for + // demos, tourney restarts, or vid_restarts + + CG_SHUTDOWN, +// void (*CG_Shutdown)( void ); + // oportunity to flush and close any open files + + CG_CONSOLE_COMMAND, +// qboolean (*CG_ConsoleCommand)( void ); + // a console command has been issued locally that is not recognized by the + // main game system. + // use Cmd_Argc() / Cmd_Argv() to read the command, return qfalse if the + // command is not known to the game + + CG_DRAW_ACTIVE_FRAME, +// void (*CG_DrawActiveFrame)( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); + // Generates and draws a game scene and status information at the given time. + // If demoPlayback is set, local movement prediction will not be enabled + + CG_CROSSHAIR_PLAYER, +// int (*CG_CrosshairPlayer)( void ); + + CG_LAST_ATTACKER +// int (*CG_LastAttacker)( void ); +} cgameExport_t; + +//---------------------------------------------- diff --git a/cgame/cg_scoreboard.c b/cgame/cg_scoreboard.c new file mode 100644 index 0000000..6b601df --- /dev/null +++ b/cgame/cg_scoreboard.c @@ -0,0 +1,2639 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_scoreboard -- draw the scoreboard on top of the game screen +#include "cg_local.h" +#include "cg_text.h" + + +#define SCOREBOARD_X (37) //Before RPG-X: (13) //(53) + +//Header info of scoreboard +#define SB_HEADER 40 //Before RPG-X: 105 //40 +#define SB_TOP (SB_HEADER+32) +#define SB_HEADERTEXT (SB_HEADER + 8) +#define SB_TOPLINE_LENGTH 550 //Before RPG-X: 590 //530 + +// Where the status bar starts, so we don't overwrite it +#define SB_STATUSBAR 420 + +//Scoreboard height +#define SB_NORMAL_HEIGHT_BIG 14 //RPG-X: RedTechie - Spacing between names +#define SB_NORMAL_HEIGHT 22 +#define SB_INTER_HEIGHT 16 //interleaved height +#define SB_RPG_X_FIXHEIGHT 350 //RPG-X: RedTechie - Fixed background height 350 before new health bar + +#define SB_MAXCLIENTS_BIG ((SB_STATUSBAR - SB_TOP) / SB_NORMAL_HEIGHT_BIG) +#define SB_MAXCLIENTS_NORMAL ((SB_STATUSBAR - SB_TOP) / SB_NORMAL_HEIGHT) +#define SB_MAXCLIENTS_INTER ((SB_STATUSBAR - SB_TOP) / SB_INTER_HEIGHT - 1) + +// Used when interleaved +#define SB_LEFT_BOTICON_X (SCOREBOARD_X+0) +#define SB_LEFT_HEAD_X (SCOREBOARD_X+12) +#define SB_RIGHT_BOTICON_X (SCOREBOARD_X+64) +#define SB_RIGHT_HEAD_X (SCOREBOARD_X+96) + +// Normal +#define SB_BOTICON_X (SCOREBOARD_X+10) //Before RPG-X - This is for positioning of class icon: (SCOREBOARD_X+20) +#define SB_HEAD_X (SCOREBOARD_X+10) //Before RPG-X - This is for positioning of head icon: (SCOREBOARD_X+50) + +//Intermission text alignment +#define SB_SCORELINE_X (SCOREBOARD_X + 76) +#define SB_CHAR_WIDTH 8 +#define SB_NAME_X (SB_SCORELINE_X + 12) +#define SB_SCORE_X (SB_SCORELINE_X + 112) +#define SB_KILLEDCNT_X (SB_SCORELINE_X + 155) +#define SB_TIME_X (SB_SCORELINE_X + 212) +#define SB_PING_X (SB_SCORELINE_X + 251) +#define SB_WORSTENEMY_X (SB_SCORELINE_X + 294) +#define SB_FAVEWEAPON_X (SB_SCORELINE_X + 398) + +#define SB_FAVETARGET_X (SB_SCORELINE_X + 182) + +//Scoreboard text alignment +#define SB_SCORELINE_X_BIG ( SCOREBOARD_X /*+ 13*/ ) //Before RPG-X: - This is the first leading space in the score line (SCOREBOARD_X + 125) +#define SB_RANK_X_BIG ( SB_SCORELINE_X_BIG + 6 ) //Before RPG-X: (SB_SCORELINE_X_BIG + 6) = 43 +#define SB_RPGCLASS_X_BIG ( SB_RANK_X_BIG + 78 ) //70 = 121 +#define SB_NAME_X_BIG ( SB_RPGCLASS_X_BIG + 46 ) //132 = 167 +#define SB_LOC_X_BIG ( SB_NAME_X_BIG + 188 ) +#define SB_SCORE_X_BIG ( SB_LOC_X_BIG + 148 ) //405 = 464 //335 //332 +#define SB_TIME_X_BIG ( SB_SCORE_X_BIG + 44 ) // = 493 +#define SB_PING_X_BIG ( SB_TIME_X_BIG + 35 ) //491 = 528 + +#define AWARD_Y 50 // height of awards medals on screen + +// The new and improved score board +// +// In cases where the number of clients is high, the score board heads are interleaved +// here's the layout + +// +// 0 32 80 112 144 240 320 400 <-- pixel position +// bot head bot head score ping time name +// +// wins/losses are drawn on bot icon now + +static qboolean localClient; // true if local client has been displayed + +static void CG_ClipString(char *buffer,char *name,int pixelCnt,int font) +{ + char str[2]; + int length; + + str[1] = '\0'; + + length = 0; + while (*name) + { + str[0] = *name; + length += UI_ProportionalStringWidth(str,font ); + + if (font == UI_TINYFONT) + { + length += PROP_GAP_TINY_WIDTH; + } + else + { + length += PROP_GAP_WIDTH; + } + + if (length > pixelCnt) + { + *buffer = '\0'; + return; + } + else + { + *buffer = *name; + } + name++; + buffer++; + } + *buffer = '\0'; +} + +/* +#define crewman 1 +#define cadet1 2 +#define cadet2 4 +#define cadet3 8 +#define cadet4 16 +#define ensign 32 +#define ltjg 64 +#define lt 128 +#define ltcmdr 256 +#define cmdr 512 +#define cpt 1024 +#define cmmdr 2048 +#define adm2 4096 +#define adm3 8192 +#define adm4 16384 +#define adm5 32768 +*/ + +//Modified by TiM for the new rank system +static void CG_DrawRank(int x, int y, pclass_t pClass, int score) { + int classno; + int score_log; + rankMenuData_t* ScoreMenuData; + + //Q_log2 + if ( ( score_log = score ) < 0 ) { //The log function takes the score and turns it into a useful + score_log = 0; //array value: ie 1024 -> 10. :) Hoorah for logarithms! + } + + classno = cgs.classData[pClass].iconColor-1; + + /*if ( !Q_stricmp( cgs.classData[pClass].iconColor, "r" ) ) { + classno = CLR_RED; + } + else if ( !Q_stricmp( cgs.classData[pClass].iconColor, "y" ) ) { + classno = CLR_GOLD; + } + else if ( !Q_stricmp( cgs.classData[pClass].iconColor, "t" ) ) { + classno = CLR_TEAL; + } + else if ( !Q_stricmp( cgs.classData[pClass].iconColor, "g" ) ) { + classno = CLR_GREEN; + } + else + classno = -1;*/ + + //based on class, choose our right color. + /*switch(pClass) + { + //Redshirt... heh heh + case PC_COMMAND: + case PC_ADMIN: + classno = CLR_RED; + break; + + //Yellowshirt + case PC_SECURITY: + case PC_ENGINEER: + classno = CLR_GOLD; + break; + + //Blueshirt + case PC_MEDICAL: + case PC_SCIENCE: + classno = CLR_TEAL; + break; + + //Marine + case PC_ALPHAOMEGA22: + classno = CLR_GREEN; + break; + + //N/A + case PC_NOCLASS: + case PC_N00B: + case PC_ALIEN: + default: + { + classno = -1; + //CG_DrawPic(x, y, 65, 14, cgs.media.ri_Civ ); + //return; + } + } */ + + //CG_Printf( "%i\n", classno ); + + //if we have a valid class and/or color + if ( classno >= 0 && cgs.ranksData[score_log].rankMenuData[classno].graphic ) { + ScoreMenuData = &cgs.ranksData[score_log].rankMenuData[classno]; + } + else { //else use our default data + ScoreMenuData = &cgs.defaultRankData.rankMenuData; + } + + //Finally, render our rank using the carefully crafted trap_R_DrawStretchPic API function :) + CG_DrawStretchPic( x, y, ScoreMenuData->w, ScoreMenuData->h, + (float)ScoreMenuData->s1/(float)ScoreMenuData->gWidth, + (float)ScoreMenuData->t1/(float)ScoreMenuData->gHeight, + (float)ScoreMenuData->s2/(float)ScoreMenuData->gWidth, + (float)ScoreMenuData->t2/(float)ScoreMenuData->gHeight, + ScoreMenuData->graphic ); + + + /*switch(score) + { + default: + case crewman: CG_DrawPic(x, y, 65, 14, cgs.media.ri_Crewman[classno] ); break; + case cadet1: CG_DrawPic(x, y, 65, 14, cgs.media.ri_Cadet1[classno] ); break; + case cadet2: CG_DrawPic(x, y, 65, 14, cgs.media.ri_Cadet2[classno] ); break; + case cadet3: CG_DrawPic(x, y, 65, 14, cgs.media.ri_Cadet3[classno] ); break; + case cadet4: CG_DrawPic(x, y, 65, 14, cgs.media.ri_Cadet4[classno] ); break; + case ensign: CG_DrawPic(x, y, 65, 14, cgs.media.ri_Ensign[classno]); break; + case ltjg: CG_DrawPic(x, y, 65, 14, cgs.media.ri_Ltjg[classno]); break; + case lt: CG_DrawPic(x, y, 65, 14, cgs.media.ri_Lt[classno]); break; + case ltcmdr: CG_DrawPic(x, y, 65, 14, cgs.media.ri_Ltcmdr[classno]); break; + case cmdr: CG_DrawPic(x, y, 65, 14, cgs.media.ri_Cmdr[classno]); break; + case cpt: CG_DrawPic(x, y, 65, 14, cgs.media.ri_Capt[classno]); break; + case cmmdr: CG_DrawPic(x, y, 65, 14, cgs.media.ri_Cmmdr[classno]); break; + case adm2: CG_DrawPic(x, y, 65, 14, cgs.media.ri_Admr2[classno]); break; + case adm3: CG_DrawPic(x, y, 65, 14, cgs.media.ri_Admr3[classno]); break; + case adm4: CG_DrawPic(x, y, 65, 14, cgs.media.ri_Admr4[classno]); break; + case adm5: CG_DrawPic(x, y, 65, 14, cgs.media.ri_Admr5[classno]); break; + }*/ +} + +static qboolean AW_Draw( void ); +/* +======================= +CG_DrawClientScoreboard_Big +======================= +*/ +static void CG_DrawClientScore_Big( int y, score_t *score, float *color, float fade, qboolean largeFormat ) { + char string[1024]; + char string2[1024]; + //RPG-X: Not Needed without a head + //vec3_t headAngles; + clientInfo_t *ci; + int picSize; + float hcolor[4]; + char *rpg_class; + vec4_t rpg_color; + vec_t *ping_txtcolor = NULL; + int intClamp; + + ci = &cgs.clientinfo[score->client]; + + // Black background + /*if (cgs.gametype < GT_TEAM) + { + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 0; + hcolor[3] = fade * 0.7; + + if (cg.numScores > SB_MAXCLIENTS_BIG) + { + //CG_FillRect( SCOREBOARD_X, y,SB_TOPLINE_LENGTH , SB_NORMAL_HEIGHT+20, hcolor ); + } + else + { + //CG_FillRect( SCOREBOARD_X, y,SB_TOPLINE_LENGTH , SB_NORMAL_HEIGHT_BIG+200, hcolor ); + } + }*/ + + //RPG-X: RedTechie - Side Strips + CG_FillRect( SCOREBOARD_X-15, SB_HEADER+20, 15, SB_RPG_X_FIXHEIGHT-4, colorTable[CT_DKBLUE1] ); //RPG-X: RedTechie - Left Strip + CG_FillRect( SB_TOPLINE_LENGTH+53, SB_HEADER+20, 15, SB_RPG_X_FIXHEIGHT-4, colorTable[CT_DKBLUE1] ); //RPG-X: RedTechie - Right Strip + + picSize = (SB_NORMAL_HEIGHT_BIG * .75); + // draw the handicap or bot skill marker (unless player has flag) + //RPG-X: RedTechie - Dont need this in RPG + /*if ( ci->powerups & ( 1 << PW_REDFLAG ) ) + { + CG_DrawFlagModel( SB_BOTICON_X, y, picSize, picSize, TEAM_RED ); + } + else if ( ci->powerups & ( 1 << PW_BORG_ADAPT ) ) + { + CG_DrawFlagModel( SB_BOTICON_X, y, picSize, picSize, TEAM_BLUE ); + } + else + { + if ( ci->pClass > PC_NOCLASS ) + { + qhandle_t icon; + //Special hack: if it's Borg who has regen going, must be Borg queen + if ( ci->pClass == PC_BORG && (ci->powerups&(1<pClass > PC_NOCLASS ) + //{ + //icon = cgs.media.pClassShaders[ci->pClass]; + //RPG-X: RedTechie - New class output + //VectorSet( rpg_color, 1.0, 1.0, 1.0 ); + + rpg_class = cgs.classData[ci->pClass].formalName; + rpg_color[0] = (float)cgs.classData[ci->pClass].radarColor[0] / 255.0f; + rpg_color[1] = (float)cgs.classData[ci->pClass].radarColor[1] / 255.0f; + rpg_color[2] = (float)cgs.classData[ci->pClass].radarColor[2] / 255.0f; + rpg_color[3] = 1.0f; + + /*switch(ci->pClass){ + case PC_COMMAND: + rpg_class = va("COMMAND"); + //VectorSet( rpg_color, 0.604, 0, 0 ); + rpg_color = colorTable[CT_RED]; + break; + case PC_SECURITY: + rpg_class = va("SECURITY"); + //VectorSet( rpg_color, 0.761, 0.537, 0.024 ); + rpg_color = colorTable[CT_YELLOW]; + break; + case PC_MEDICAL: + rpg_class = va("MEDICAL"); + //VectorSet( rpg_color, 0.082, 0.337, 0.357 ); + rpg_color = colorTable[CT_BLUE]; + break; + case PC_ADMIN: + rpg_class = va("ADMIN"); + rpg_color = colorTable[CT_LTORANGE]; + //VectorCopy( colorTable[CT_LTORANGE], rpg_color ); + break; + case PC_N00B: + rpg_class = va("N00b"); + rpg_color = colorTable[CT_LTORANGE]; + //VectorCopy( colorTable[CT_LTORANGE], rpg_color ); + break; + case PC_SCIENCE: + rpg_class = va("SCIENCE"); + rpg_color = colorTable[CT_BLUE]; + //VectorSet( rpg_color, 0.082, 0.337, 0.357 ); + break; + case PC_ENGINEER: + rpg_class = va("ENGINEER"); + rpg_color = colorTable[CT_YELLOW]; + //VectorSet( rpg_color, 0.761, 0.537, 0.024 ); + break; + case PC_ALIEN: + rpg_class = va("ALIEN"); + rpg_color = colorTable[CT_LTORANGE]; + //VectorCopy( colorTable[CT_LTORANGE], rpg_color ); + break; + case PC_ALPHAOMEGA22: + rpg_class = va("MARINE"); + rpg_color = colorTable[CT_GREEN]; + //VectorSet( rpg_color, 0.012, 0.443, 0.047 ); + break; + case PC_NOCLASS: + rpg_class = va(""); + rpg_color = colorTable[CT_WHITE]; + //VectorCopy( colorTable[CT_WHITE], rpg_color ); + break; + }*/ + //rpg_color[3] = 1.0f; //100% alpha + //} + + //} + //RPG-X: RedTechie - Dont need this in RPG + /*else if ( ci->botSkill > 0 && ci->botSkill <= 5 ) + { + if ( cg_drawIcons.integer ) + { + CG_DrawPic( SB_BOTICON_X, y, picSize, picSize, cgs.media.botSkillShaders[ ci->botSkill - 1 ] ); + } + } + else if ( ci->handicap < 100 ) + { + Com_sprintf( string, sizeof( string ), "%i", ci->handicap ); + if ( cgs.gametype == GT_TOURNAMENT ) + { +// CG_DrawSmallStringColor( iconx, y - SMALLCHAR_HEIGHT/2, string, color ); + UI_DrawProportionalString( SB_BOTICON_X, y - SMALLCHAR_HEIGHT/2, string, UI_SMALLFONT, color); + } + else + { +// CG_DrawSmallStringColor( iconx, y, string, color ); + UI_DrawProportionalString( SB_BOTICON_X, y , string, UI_SMALLFONT, color); + } + } + + // draw the wins / losses + if ( cgs.gametype == GT_TOURNAMENT ) { + Com_sprintf( string, sizeof( string ), "%i/%i", ci->wins, ci->losses ); + if( ci->handicap < 100 && !ci->botSkill ) + { +// CG_DrawSmallStringColor( iconx, y + SMALLCHAR_HEIGHT/2, string, color ); + UI_DrawProportionalString( SB_BOTICON_X, y + SMALLCHAR_HEIGHT/2 , string, UI_SMALLFONT, color); + } + else + { +// CG_DrawSmallStringColor( iconx, y, string, color ); + UI_DrawProportionalString( SB_BOTICON_X, y , string, UI_SMALLFONT, color); + } + } + }*/ + //RPG-X: No face needed in new scoreboard + // draw the face + //VectorClear( headAngles ); + //headAngles[YAW] = 180; + //CG_DrawHead( SB_HEAD_X, y, picSize, picSize, score->client, headAngles ); + + + // highlight your position + if ( score->client == cg.snap->ps.clientNum ) + { + int rank; + + localClient = qtrue; + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR + || cgs.gametype >= GT_TEAM ) + { + rank = -1; + } + else + { + rank = cg.snap->ps.persistant[PERS_RANK] & ~RANK_TIED_FLAG; + } + if ( rank == 0 ) + { + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 0.7; + } + else if ( rank == 1 ) + { + hcolor[0] = 0.7; + hcolor[1] = 0; + hcolor[2] = 0; + } + else if ( rank == 2 ) + { + hcolor[0] = 0.7; + hcolor[1] = 0.7; + hcolor[2] = 0; + } + else + { + hcolor[0] = 0.7; + hcolor[1] = 0.7; + hcolor[2] = 0.7; + } + + hcolor[3] = fade * 0.7; + //RPG-X: RedTechie No highlighting player info in rpg + /* CG_FillRect( SB_SCORELINE_X_BIG - SMALLCHAR_WIDTH, y, + SB_TOPLINE_LENGTH - ((SB_SCORELINE_X_BIG - SMALLCHAR_WIDTH) - SCOREBOARD_X), BIGCHAR_HEIGHT+1, hcolor ); + */ + } + + + // draw the score line + //RPG-X: Redtechie - Ping color + if(score->ping >= 501){ + ping_txtcolor = colorTable[CT_RED]; + }else if(score->ping >= 230){ + ping_txtcolor = colorTable[CT_DKRED1]; + }else if(score->ping >= 118){ + ping_txtcolor = colorTable[CT_YELLOW]; + }else if(score->ping <= 117){ + ping_txtcolor = colorTable[CT_WHITE]; + } + + //TiM : Re-structured slightly for lesser redundancy. + //Player is connecting + if ( score->ping == -1 ) { + Com_sprintf(string,sizeof(string),"(%s)%s",ingame_text[IGT_CONNECTING],ci->name); + UI_DrawProportionalString( SB_NAME_X_BIG, y , string, UI_TINYFONT, colorTable[CT_WHITE]); + } + else { + int clipLength; + + //draw rank image + CG_DrawRank( SB_RANK_X_BIG, y-2,ci->pClass, score->score); + + //draw player class name + if(rpg_class && rpg_color){ + UI_DrawProportionalString( SB_RPGCLASS_X_BIG, y , rpg_class, UI_TINYFONT | UI_LEFT, rpg_color); //CT_VLTPURPLE1 CT_WHITE + } + + //if we have locations, change the cliplength of the name field + if ( cgs.locations ) { + clipLength = 185; + } + else { + clipLength = 327; + } + + //draw player name with varying instances + //If spect, concat a spect msg next to it + if ( ci->team == TEAM_SPECTATOR ) { + Com_sprintf(string,sizeof(string),"(%s)%s",ingame_text[IGT_SPECTABBREV],ci->name); + CG_ClipString(string2,string,clipLength,UI_TINYFONT); //RPG-X ADDED: RedTechie - 200 pixels in the name column use to be 184 + UI_DrawProportionalString( SB_NAME_X_BIG, y , string2, UI_TINYFONT | UI_LEFT, colorTable[CT_WHITE]); + } + //if lagged out, add a lagged out msg + else if ( score->ping >= 827 ) + { + Com_sprintf(string,sizeof(string),"(%s)%s",ingame_text[IGT_PINGEDOUT],ci->name); + CG_ClipString(string2,string,clipLength,UI_TINYFONT); //RPG-X ADDED: RedTechie - 200 pixels in the name column use to be 184 + UI_DrawProportionalString( SB_NAME_X_BIG, y , string2, UI_TINYFONT | UI_LEFT, colorTable[CT_WHITE]); + } + //else, draw the name normally + else + { + CG_ClipString(string,ci->name,clipLength,UI_TINYFONT); //RPG-X ADDED: RedTechie - 200 pixels in the name column use to be 184 + UI_DrawProportionalString( SB_NAME_X_BIG, y , string, UI_TINYFONT | UI_LEFT, colorTable[CT_WHITE]); + } + + //Player locations + if ( cgs.locations ) { + CG_ClipString(string, va("%s", CG_ConfigString( CS_LOCATIONS + ci->location ) ), 495, UI_TINYFONT); //RPG-X ADDED: RedTechie - 200 pixels in the name column use to be 184 + UI_DrawProportionalString( SB_LOC_X_BIG, y , string, UI_TINYFONT | UI_LEFT, colorTable[CT_WHITE]); + } + + //player client Num + //Com_sprintf(string,sizeof(string), "%i", intClamp); //RPG-X: J2J Switched Scoore to Client No. + intClamp = Com_Clamp( 0, 128, cg_entities[score->client].currentState.clientNum ); + UI_DrawProportionalString( SB_SCORE_X_BIG, y , va("%i", intClamp), UI_TINYFONT | UI_LEFT, colorTable[CT_WHITE]); + + //player time + //If someone actually hits this limit, they officially have no life. + //Com_sprintf(string,sizeof(string),"%i", intClamp); + intClamp = Com_Clamp( 0, 99999, score->time ); + UI_DrawProportionalString( SB_TIME_X_BIG, y , va("%i", intClamp), UI_TINYFONT | UI_LEFT, colorTable[CT_WHITE]); + + //player ping + //Com_sprintf(string,sizeof(string),"%i",score->ping); + intClamp = Com_Clamp( 0, 999, score->ping ); + UI_DrawProportionalString( SB_PING_X_BIG, y, va("%i", intClamp), UI_TINYFONT | UI_LEFT, ping_txtcolor); + + } + + /*//RPG-X: RedTechie - Connecting + if ( score->ping == -1 ) { + Com_sprintf(string,sizeof(string),"(%s)%s",ingame_text[IGT_CONNECTING],ci->name); + UI_DrawProportionalString( SB_NAME_X_BIG, y , string, UI_TINYFONT, colorTable[CT_WHITE]); + } + //J2J Book + //RPG-X: RedTechie - Spectator + + else if ( ci->team == TEAM_SPECTATOR ) + { + Com_sprintf(string,sizeof(string),"(%s)%s",ingame_text[IGT_SPECTABBREV],ci->name); + CG_NamePrep(string2,string,200,UI_TINYFONT); //RPG-X ADDED: RedTechie - 200 pixels in the name column use to be 184 + UI_DrawProportionalString( SB_NAME_X_BIG, y , string2, UI_TINYFONT, colorTable[CT_WHITE]); + if(rpg_class && rpg_color){ + UI_DrawProportionalString( SB_RPGCLASS_X_BIG, y , rpg_class, UI_TINYFONT, rpg_color); //CT_VLTPURPLE1 CT_WHITE + } + Com_sprintf(string,sizeof(string),"%5i",cg_entities[score->client].currentState.clientNum); //RPG-X: J2J Switched Scoore to Client No. + UI_DrawProportionalString( SB_SCORE_X_BIG, y , string, UI_TINYFONT, colorTable[CT_WHITE]); + + Com_sprintf(string,sizeof(string),"%5i",score->time); + UI_DrawProportionalString( SB_TIME_X_BIG, y , string, UI_TINYFONT, colorTable[CT_WHITE]); + + Com_sprintf(string,sizeof(string),"%5i",score->ping); + UI_DrawProportionalString( SB_PING_X_BIG, y, string, UI_TINYFONT, ping_txtcolor); + CG_DrawRank(SB_RANK_X_BIG, y-2,ci->pClass, score->score); + } + //RPG-X: RedTechie - Pinged out + else if ( score->ping >= 827 ) + { + Com_sprintf(string,sizeof(string),"(%s)%s",ingame_text[IGT_PINGEDOUT],ci->name); + CG_NamePrep(string2,string,200,UI_TINYFONT); //RPG-X ADDED: RedTechie - 200 pixels in the name column use to be 184 + UI_DrawProportionalString( SB_NAME_X_BIG, y , string2, UI_TINYFONT, colorTable[CT_WHITE]); + if(rpg_class && rpg_color){ + UI_DrawProportionalString( SB_RPGCLASS_X_BIG, y , rpg_class, UI_TINYFONT, rpg_color); //CT_VLTPURPLE1 CT_WHITE + } + Com_sprintf(string,sizeof(string),"%5i",cg_entities[score->client].currentState.clientNum); //RPG-X: J2J Switched Scoore to Client No. + UI_DrawProportionalString( SB_SCORE_X_BIG, y , string, UI_TINYFONT, colorTable[CT_WHITE]); + + Com_sprintf(string,sizeof(string),"%5i",score->time); + UI_DrawProportionalString( SB_TIME_X_BIG, y , string, UI_TINYFONT, colorTable[CT_WHITE]); + + Com_sprintf(string,sizeof(string),"%5i",score->ping); + UI_DrawProportionalString( SB_PING_X_BIG, y, string, UI_TINYFONT, ping_txtcolor); + CG_DrawRank(SB_RANK_X_BIG, y-2,ci->pClass, score->score); + } + //RPG-X: RedTechie - Regular + else + { + //draw rank image + CG_DrawRank( SB_RANK_X_BIG, y-2,ci->pClass, score->score); + + //draw player class name + if(rpg_class && rpg_color){ + UI_DrawProportionalString( SB_RPGCLASS_X_BIG, y , rpg_class, UI_TINYFONT, rpg_color); //CT_VLTPURPLE1 CT_WHITE + } + + //prep name and then render it + CG_NamePrep(string,ci->name,200,UI_TINYFONT); //RPG-X ADDED: RedTechie - 200 pixels in the name column use to be 184 + UI_DrawProportionalString( SB_NAME_X_BIG, y , string, UI_TINYFONT, colorTable[CT_WHITE]); + + //player client Num + Com_sprintf(string,sizeof(string),"%3i",cg_entities[score->client].currentState.clientNum); //RPG-X: J2J Switched Scoore to Client No. + UI_DrawProportionalString( SB_SCORE_X_BIG, y , string, UI_TINYFONT, colorTable[CT_WHITE]); + + //player time + Com_sprintf(string,sizeof(string),"%5i",score->time); + UI_DrawProportionalString( SB_TIME_X_BIG, y , string, UI_TINYFONT, colorTable[CT_WHITE]); + + //player ping + Com_sprintf(string,sizeof(string),"%3i",score->ping); + UI_DrawProportionalString( SB_PING_X_BIG, y, string, UI_TINYFONT, ping_txtcolor); + }*/ + + //RPG-X: RedTechie - Draw live divider after every client + CG_FillRect( SB_NAME_X_BIG, y+11, SB_TOPLINE_LENGTH-119, 1, colorTable[CT_VDKBLUE2] ); // y off - 10, thickness - 2 //150 + +} + +/* +======================= +CG_DrawClientScoreboard +======================= +*/ +static void CG_DrawClientScore( int y, score_t *score, float *color, float fade, qboolean largeFormat ) { + //char string[1024]; + //char string2[1024]; + //vec3_t headAngles; + clientInfo_t *ci; + //int picSize; + //char /**worstEnemy,*/*faveWeapon=0; + //float hcolor[4]; + //gitem_t * item; + int inIntermission/*,length*/; + + inIntermission = ( + (cg.snap->ps.pm_type==PM_INTERMISSION) + || (cg.intermissionStarted) + || (cg.predictedPlayerState.pm_type==PM_INTERMISSION) ); + + + if ( score->client < 0 || score->client >= cgs.maxclients ) + { + Com_Printf( "Bad score->client: %i\n", score->client ); + return; + } + + + ci = &cgs.clientinfo[score->client]; + if (!ci->infoValid) + { + Com_Printf( "Bad clientInfo: %i\n", score->client ); + return; + } + + /*trap_R_SetColor( colorTable[CT_DKBLUE1] ); + CG_DrawPic( SCOREBOARD_X-15, y-28, 25, 28, cgs.media.scoreboardtopleft ); //RPG-X: - RedTechie Top Left + CG_DrawPic( SB_TOPLINE_LENGTH+43, y-28, 25, 28, cgs.media.scoreboardtopright ); //RPG-X: - RedTechie Top Right + CG_DrawPic( SCOREBOARD_X-15, y+325, 36, 28, cgs.media.scoreboardbotleft ); //RPG-X: - RedTechie Bottom Left + CG_DrawPic( SB_TOPLINE_LENGTH+32, y+321, 36, 32, cgs.media.scoreboardbotright ); //RPG-X: - RedTechie Bottom Right + */ + //RPG-X: - RedTechie Fixed intermissions scoreboard + if ( !inIntermission ) + { + //RPG-X BOOKMARK + CG_DrawClientScore_Big(y, score, color, fade, largeFormat ); + return; + }else{ + CG_DrawClientScore_Big(y, score, color, fade, largeFormat ); + return; + } + //TiM : From the looks of this, nothing after this is acctually called O_o + //Save graphics then + + // Black background + /*if (cgs.gametype < GT_TEAM) + { + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 0; + hcolor[3] = fade * 0.7; + CG_FillRect( SCOREBOARD_X, y,SB_TOPLINE_LENGTH , SB_NORMAL_HEIGHT, hcolor ); + }*/ + + // Left Side of scoreboard + //CG_FillRect( SCOREBOARD_X, y - 16 , 12, 48, colorTable[CT_DKORANGE]); + + +/* + picSize = 20; + // draw the handicap or bot skill marker (unless player has flag) + if ( ci->powerups & ( 1 << PW_REDFLAG ) ) + { + CG_DrawFlagModel( SB_BOTICON_X, y, picSize, picSize, TEAM_RED ); + } + else if ( ci->powerups & ( 1 << PW_BORG_ADAPT ) ) + { + CG_DrawFlagModel( SB_BOTICON_X, y, picSize, picSize, TEAM_BLUE ); + } + else + { + if ( ci->botSkill > 0 && ci->botSkill <= 5 ) + { + if ( cg_drawIcons.integer ) + { + CG_DrawPic( SB_BOTICON_X, y+2, picSize, picSize, cgs.media.botSkillShaders[ ci->botSkill - 1 ] ); + } + } + else if ( ci->handicap < 100 ) + { + Com_sprintf( string, sizeof( string ), "%i", ci->handicap ); + if ( cgs.gametype == GT_TOURNAMENT ) + { +// CG_DrawSmallStringColor( iconx, y - SMALLCHAR_HEIGHT/2, string, color ); + UI_DrawProportionalString( SB_BOTICON_X+2, y - SMALLCHAR_HEIGHT/2, string, UI_SMALLFONT, color); + } + else + { +// CG_DrawSmallStringColor( iconx, y, string, color ); + UI_DrawProportionalString( SB_BOTICON_X+2, y , string, UI_SMALLFONT, color); + } + } + + // draw the wins / losses + if ( cgs.gametype == GT_TOURNAMENT ) { + Com_sprintf( string, sizeof( string ), "%i/%i", ci->wins, ci->losses ); + if( ci->handicap < 100 && !ci->botSkill ) + { +// CG_DrawSmallStringColor( iconx, y + SMALLCHAR_HEIGHT/2, string, color ); + UI_DrawProportionalString( SB_BOTICON_X, y + SMALLCHAR_HEIGHT/2 , string, UI_SMALLFONT, color); + } + else + { +// CG_DrawSmallStringColor( iconx, y, string, color ); + UI_DrawProportionalString( SB_BOTICON_X, y , string, UI_SMALLFONT, color); + } + } + }*/ + + // draw the face + /*VectorClear( headAngles ); + headAngles[YAW] = 180; + CG_DrawHead( SB_HEAD_X+14, y, picSize, picSize, score->client, headAngles );//Before RPG-X: CG_DrawHead( SB_HEAD_X+14, y, picSize, picSize, score->client, headAngles ); + + // highlight your position + if ( score->client == cg.snap->ps.clientNum ) + { + int rank; + + localClient = qtrue; + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR + || cgs.gametype >= GT_TEAM ) + { + rank = -1; + } + else + { + rank = cg.snap->ps.persistant[PERS_RANK] & ~RANK_TIED_FLAG; + } + if ( rank == 0 ) + { + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 0.7; + } + else if ( rank == 1 ) + { + hcolor[0] = 0.7; + hcolor[1] = 0; + hcolor[2] = 0; + } + else if ( rank == 2 ) + { + hcolor[0] = 0.7; + hcolor[1] = 0.7; + hcolor[2] = 0; + } + else + { + hcolor[0] = 0.7; + hcolor[1] = 0.7; + hcolor[2] = 0.7; + } + + hcolor[3] = fade * 0.7; + CG_FillRect( SB_SCORELINE_X - TINYCHAR_WIDTH, y, + SB_TOPLINE_LENGTH - ((SB_SCORELINE_X - TINYCHAR_WIDTH) - SCOREBOARD_X), BIGCHAR_HEIGHT+1, hcolor ); + } + + // draw the score line + if ( score->ping == -1 ) { + Com_sprintf(string, sizeof(string), + " connecting %s", ci->name); + UI_DrawProportionalString( SB_SCORELINE_X, y, string, UI_TINYFONT, colorTable[CT_LTGOLD1]); + } + else if ( ci->team == TEAM_SPECTATOR ) + { + CG_NamePrep(string,ci->name,100,UI_TINYFONT); //100 pixels in the name column + + Com_sprintf(string,sizeof(string),"%s (%s)", string, ingame_text[IGT_SPECTATOR]); + UI_DrawProportionalString( SB_NAME_X, y + (TINYCHAR_HEIGHT/2), string, UI_TINYFONT, colorTable[CT_LTGOLD1]); + + // Don't show other stats for Spectators +// Com_sprintf(string,sizeof(string),"%5i",score->score); +// UI_DrawProportionalString( SB_SCORE_X, y + (TINYCHAR_HEIGHT/2), string, UI_TINYFONT, colorTable[CT_YELLOW]); + +// Com_sprintf(string,sizeof(string),"%5i",score->time); +// UI_DrawProportionalString( SB_TIME_X, y + (TINYCHAR_HEIGHT/2), string, UI_TINYFONT, colorTable[CT_LTGOLD1]); + +// Com_sprintf(string,sizeof(string),"%5i",score->ping); +// UI_DrawProportionalString( SB_PING_X, y + (TINYCHAR_HEIGHT/2), string, UI_TINYFONT, colorTable[CT_LTGOLD1]); + +// Com_sprintf(string,sizeof(string),"%5i",score->killedCnt); +// UI_DrawProportionalString( SB_KILLEDCNT_X, y + (TINYCHAR_HEIGHT/2), string, UI_TINYFONT, colorTable[CT_RED]); + } + else + {*/ + /* + if (score->faveTarget >= 0) + { + faveTarget = cgs.clientinfo[score->faveTarget].name; + } + else + { + faveTarget = ingame_text[IGT_NONE]; + } + */ + /*if (score->worstEnemy >= 0) + { + worstEnemy = cgs.clientinfo[score->worstEnemy].name; + } + else + { + worstEnemy = ingame_text[IGT_NONE]; + } + + if (score->faveWeapon > 0) + { + faveWeapon = ingame_text[IGT_NONE]; + // Find weapon + for ( item = bg_itemlist + 1 ; item->classname ; item++ ) + { + if ( item->giType != IT_WEAPON ) { + continue; + } + if (item->giTag == score->faveWeapon) + { + faveWeapon = item->pickup_name; + break; + } + } + } + +// Com_sprintf(string, sizeof(string), +// "%-20s %5i %5i %-20s (%5i) %-20s (%5i)",ci->name, score->score, score->time, +// faveTarget, score->faveTargetKills, +// worstEnemy, score->worstEnemyKills); + + CG_NamePrep(string,ci->name,100,UI_TINYFONT); //100 pixels in the name column + + //RPG-X: J2J (NOTE TO CODERS)- Heading for score board -DO NOT EDIT- Text for these are in mp_ingametext.dat + Com_sprintf(string,sizeof(string),"%5i", score->client); + UI_DrawProportionalString( SB_NAME_X-20, y + (TINYCHAR_HEIGHT/2), string, UI_TINYFONT, colorTable[CT_WHITE]); + + UI_DrawProportionalString( SB_NAME_X, y + (TINYCHAR_HEIGHT/2), string, UI_TINYFONT, colorTable[CT_WHITE]); + Com_sprintf(string,sizeof(string),"%5i",score->score); + UI_DrawProportionalString( SB_SCORE_X, y + (TINYCHAR_HEIGHT/2), string, UI_TINYFONT, colorTable[CT_YELLOW]); + + Com_sprintf(string,sizeof(string),"%5i",score->time); + UI_DrawProportionalString( SB_TIME_X, y + (TINYCHAR_HEIGHT/2), string, UI_TINYFONT, colorTable[CT_LTGOLD1]); + + Com_sprintf(string,sizeof(string),"%5i",score->ping); + UI_DrawProportionalString( SB_PING_X, y + (TINYCHAR_HEIGHT/2), string, UI_TINYFONT, colorTable[CT_LTGOLD1]); + + Com_sprintf(string,sizeof(string),"%5i",score->killedCnt); + UI_DrawProportionalString( SB_KILLEDCNT_X, y + (TINYCHAR_HEIGHT/2), string, UI_TINYFONT, colorTable[CT_RED]); + + if (worstEnemy) + { + Com_sprintf(string2,sizeof(string2)," (%i)",score->worstEnemyKills); + length = UI_ProportionalStringWidth(string2,UI_TINYFONT ); + + CG_NamePrep(string,worstEnemy,(100-length),UI_TINYFONT); //100 pixels in the worst enemy column + + Com_sprintf(string,sizeof(string),"%s%s",string, string2); + UI_DrawProportionalString( SB_WORSTENEMY_X, y + (TINYCHAR_HEIGHT/2), string, UI_TINYFONT, colorTable[CT_RED]); + } + + if (faveWeapon) + { + UI_DrawProportionalString( SB_FAVEWEAPON_X, y + (TINYCHAR_HEIGHT/2), faveWeapon, UI_TINYFONT, colorTable[CT_LTGOLD1]); + } + } + + //CG_DrawSmallString( SB_SCORELINE_X, y, string, fade ); + + // add the "ready" marker for intermission exiting, if not a bot, and if a team type game + if ( inIntermission ) + { + qhandle_t h = trap_R_RegisterShader("icons/icon_ready_on"), + h2 = trap_R_RegisterShader("icons/icon_ready_off"); + if ( ci->botSkill > 0 && ci->botSkill <= 5 ) + { + // i'm a bot. i'm always ready. + trap_R_SetColor( colorTable[CT_VLTGOLD1]); + CG_DrawPic( SB_BOTICON_X+26,y+2, 16, 16,h); + } + else + { + if ( cg.snap->ps.stats[ STAT_CLIENTS_READY ] & ( 1 << score->client ) ) + { + //trap_R_SetColor( colorTable[CT_VLTGOLD1]); + //CG_DrawPic( SB_BOTICON_X+26,y+2, 16, 16,h); + } + else// if (score->client) + { + //trap_R_SetColor( colorTable[CT_VLTGOLD1]); + //CG_DrawPic( SB_BOTICON_X+26,y+2, 16, 16,h2); + } + } + }*/ +} + +static int CG_GetTeamCount(team_t team, int maxClients) +{ + int i = 0; + int count = 0; + score_t *score = NULL; + clientInfo_t *ci = NULL; + + for ( i = 0 ; i < cg.numScores && count < maxClients ; i++ ) + { + score = &cg.scores[i]; + ci = &cgs.clientinfo[ score->client ]; + + if ( team!=ci->team || !ci->infoValid ) + { + continue; + } + count++; + } + + return count; +} + +/* +================= +CG_TeamScoreboard +================= +*/ +static int CG_TeamScoreboard( int y, team_t team, float fade, int maxClients, int lineHeight ) { + int i; + score_t *score; + float color[4]; + int count; + clientInfo_t *ci; + + color[0] = color[1] = color[2] = 1.0; + color[3] = fade; + + count = 0; + + for ( i = 0 ; i < cg.numScores && count < maxClients ; i++ ) { + score = &cg.scores[i]; + ci = &cgs.clientinfo[ score->client ]; + + if ( team != ci->team ) { + continue; + } + + if (ci->infoValid) + { + //RPG-X BOOKMARK + //UI_DrawProportionalString(60,y + lineHeight * count,va("%i",cg_entities[i].currentState.clientNum), UI_TINYFONT, colorTable[CT_LTGOLD1]); + CG_DrawClientScore( y + lineHeight * count, score, color, fade, lineHeight == SB_NORMAL_HEIGHT ); + count++; + } + } + + return count; +} + +void CG_AddGameModNameToGameName( char *gamename ) +{ + //Primary Mod + if ( cgs.pModElimination ) + { + strcat( gamename, ": " ); + strcat( gamename, ingame_text[IGT_GAME_ELIMINATION] ); + } + else if ( cgs.pModAssimilation ) + { + strcat( gamename, ": " ); + strcat( gamename, ingame_text[IGT_GAME_ASSIMILATION] ); + } + else if ( cgs.pModActionHero ) + { + strcat( gamename, ": " ); + strcat( gamename, ingame_text[IGT_GAME_ACTIONHERO] ); + } + + //Secondary Mod + if ( cgs.pModSpecialties ) + { + strcat( gamename, " (" ); + strcat( gamename, ingame_text[IGT_GAME_SPECIALTIES] ); + strcat( gamename, ")" ); + } + else if ( cgs.pModDisintegration ) + { + strcat( gamename, " (" ); + strcat( gamename, ingame_text[IGT_GAME_DISINTEGRATION] ); + strcat( gamename, ")" ); + } +} + +/* +================= +CG_DrawScoreboard + +Draw the normal in-game scoreboard +================= +*/ +qboolean CG_DrawScoreboard( void ) +{ + int y, i, n1 = 0, n2 = 0; // n3 = 0; + float fade; + float *fadeColor; + char *s; + //RPG-X: RedTechie dont need buf with all the print team code commented out + //char buf[64]; + int maxClients; + int lineHeight; + int topBorderSize, bottomBorderSize; + int rankOfClient; + int tTeam/*, bTeam*/; + float hcolor[4]; + int inIntermission; + char gamename[1024]; + //int gOffset = 20; + + inIntermission = ( + (cg.snap->ps.pm_type==PM_INTERMISSION) + || (cg.intermissionStarted) + || (cg.predictedPlayerState.pm_type==PM_INTERMISSION) ); + + // DO NOT SHOW THE SCOREBOARD IF: + //////////////////////////////////////////////////////////////// + // 1) Menu or Console is Up + if ( cg_paused.integer ) + { cg.deferredPlayerLoading = 0; + return qfalse; } + // + // 2) Awards Ceremony is not finished during Intermisison + if (inIntermission && !AW_Draw()) + { return qfalse; } + // + // 3) If we are doing a warmup + if (cg.warmup && !cg.showScores) + return qfalse; + //RPG-X: RedTechie - Hacked so scoreboard dosnt show when players die + if(cg.predictedPlayerState.pm_type==PM_DEAD){ + return qfalse; + } + + tTeam = TEAM_RED; // Compiler needed initialization here for some reason... + + // FADE SETUP... ?? + if ( cg.showScores || cg.predictedPlayerState.pm_type == PM_DEAD || + inIntermission ) + { + fade = 1.0; + fadeColor = colorWhite; + } else + { + fadeColor = CG_FadeColor( cg.scoreFadeTime, FADE_TIME ); + + if ( !fadeColor ) + { + // next time scoreboard comes up, don't print killer + cg.deferredPlayerLoading = 0; + cg.killerName[0] = 0; + return qfalse; + } + fade = *fadeColor; + } + + + // IN GAME SCOREBOARD HEADER STUFF + //--------------------------------- + rankOfClient = cg.snap->ps.persistant[PERS_RANK]; + + if ( !inIntermission && cg.snap->ps.persistant[PERS_TEAM]!=TEAM_SPECTATOR) + { + // Display Your Rank / Your Team + //----------------------------------------------- + //RPG-X: RedTechie Dont need this for rpg This code prints out what team your on and if your winning when you call the scoreboard just takes up to much room (i'll probly muck it all up but here it goes + /*if (cgs.gametype<=GT_SINGLE_PLAYER) + { + if (rankOfClient & RANK_TIED_FLAG) + Com_sprintf( buf, sizeof(buf), "%s %i", ingame_text[IGT_TIED_FOR], (rankOfClient&~RANK_TIED_FLAG)+1); + else + Com_sprintf( buf, sizeof(buf), "%s %i", ingame_text[IGT_YOUR_RANK], (rankOfClient&~RANK_TIED_FLAG)+1); + UI_DrawProportionalString( 14, AWARD_Y, buf, UI_BIGFONT|UI_LEFT, colorTable[CT_LTGOLD1] ); + } + if (cgs.gametype>GT_SINGLE_PLAYER) + { + if ( cg.teamScores[0] == cg.teamScores[1] ) + s = va("%s %i", ingame_text[IGT_TEAMSARETIED],cg.teamScores[0] ); + else if ( cg.teamScores[0] >= cg.teamScores[1] ) + s = va("%s %i %s %i",ingame_text[IGT_REDTEAMLEADS],cg.teamScores[0],ingame_text[IGT_TO], cg.teamScores[1] ); + else + s = va("%s %i %s %i",ingame_text[IGT_BLUETEAMLEADS],cg.teamScores[1],ingame_text[IGT_TO], cg.teamScores[0] ); + + UI_DrawProportionalString(14, AWARD_Y, s, UI_BIGFONT|UI_LEFT, colorTable[CT_LTGOLD1]); + }*/ + // Display Who Killed You + //----------------------------------------------- + if ( cg.killerName[0] ) + { + if ( cg.mod == MOD_ASSIMILATE ) + { + s = va("%s %s",ingame_text[IGT_ASSIMILATEDBY], cg.killerName ); + } + else + { + s = va("%s %s",ingame_text[IGT_FRAGGEDBY], cg.killerName ); + } + UI_DrawProportionalString((SCREEN_WIDTH/2), 438, s, UI_BIGFONT|UI_CENTER, colorTable[CT_CYAN]);//RPG-X: - RedTechie Y axis use to be 75 + } + } + // scoreboard + y = SB_HEADER; + + // Top of scoreboard + //trap_R_SetColor( colorTable[CT_DKORANGE] ); +// CG_DrawPic( SCOREBOARD_X, y + 27, 16, -32, cgs.media.corner_12_24 ); // Corner +// CG_DrawPic( SCOREBOARD_X, y, 16, 32, cgs.media.corner_12_24 ); // Corner + //RPG-X: Bookmark + //CG_FillRect( SCOREBOARD_X, y, SB_TOPLINE_LENGTH, 24, colorTable[CT_DKORANGE]); + + // Black background + //if (cgs.gametype < GT_TEAM) + //{ + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 0; + hcolor[3] = fade * 0.7; + //CG_FillRect( SCOREBOARD_X, y + 20,SB_TOPLINE_LENGTH, 12, hcolor ); + //RPG-X: RedTechie - Fixed black background height + CG_FillRect( SCOREBOARD_X, y + 5,SB_TOPLINE_LENGTH + 26, SB_RPG_X_FIXHEIGHT+15, hcolor );//RPG-X: RedTechie - Before CG_FillRect( SCOREBOARD_X, y + 20,SB_TOPLINE_LENGTH, SB_RPG_X_FIXHEIGHT, hcolor ); + //} + + trap_R_SetColor( colorTable[CT_DKBLUE1] ); + CG_DrawPic( SCOREBOARD_X-15, SB_TOP-28, 25, 28, cgs.media.scoreboardtopleft ); //RPG-X: - RedTechie Top Left + CG_DrawPic( SB_TOPLINE_LENGTH+43, SB_TOP-28, 25, 28, cgs.media.scoreboardtopright ); //RPG-X: - RedTechie Top Right + CG_DrawPic( SCOREBOARD_X-15, SB_TOP+325, 36, 28, cgs.media.scoreboardbotleft ); //RPG-X: - RedTechie Bottom Left + CG_DrawPic( SB_TOPLINE_LENGTH+32, SB_TOP+321, 36, 32, cgs.media.scoreboardbotright ); //RPG-X: - RedTechie Bottom Right + +/*#define SB_RANK_X_BIG ( SB_SCORELINE_X_BIG + 6 ) //Before RPG-X: (SB_SCORELINE_X_BIG + 6) = 43 +#define SB_RPGCLASS_X_BIG ( SB_RANK_X_BIG + 78 ) //70 = 121 +#define SB_NAME_X_BIG ( SB_RPGCLASS_X_BIG + 46 ) //132 = 167 +#define SB_LOC_X_BIG ( SB_NAME_X_BIG + 187 ) +#define SB_SCORE_X_BIG ( SB_LOC_X_BIG + 148 ) //405 = 464 //335 //332 +#define SB_TIME_X_BIG ( SB_SCORE_X_BIG + 44 ) // = 493 +#define SB_PING_X_BIG ( SB_TIME_X_BIG + 35 ) //491 = 528*/ + + CG_FillRect( SCOREBOARD_X, y+5, ( SB_RPGCLASS_X_BIG - 5 ) - SCOREBOARD_X, 15, colorTable[CT_DKBLUE1]); //RPG-X: Rank Bar //75 //79 + CG_FillRect( SB_RPGCLASS_X_BIG - 3, y+5, ( SB_NAME_X_BIG - 5 ) - ( SB_RPGCLASS_X_BIG - 3 ), 15, colorTable[CT_DKBLUE1]); //RPG-X: Class Bar //46 - 60 + if ( cgs.locations ) { + CG_FillRect( SB_NAME_X_BIG - 3, y+5, ( SB_LOC_X_BIG - 5 ) - ( SB_NAME_X_BIG - 3 ), 15, colorTable[CT_DKBLUE1]); //RPG-X: Name Bar //269 + CG_FillRect( SB_LOC_X_BIG - 3, y+5, ( SB_SCORE_X_BIG - 5 ) - ( SB_LOC_X_BIG - 3 ), 15, colorTable[CT_DKBLUE1]); //RPG-X: Locations Bar //269 + } + else { + CG_FillRect( SB_NAME_X_BIG - 3, y+5, ( SB_SCORE_X_BIG - 5 ) - ( SB_NAME_X_BIG - 3 ), 15, colorTable[CT_DKBLUE1]); //RPG-X: Name Bar //269 + } + + CG_FillRect( SB_SCORE_X_BIG - 3, y+5, ( SB_TIME_X_BIG - 5 ) - ( SB_SCORE_X_BIG - 3 ), 15, colorTable[CT_DKBLUE1]); //RPG-X: Client ID Bar //47 + CG_FillRect( SB_TIME_X_BIG - 3, y+5, ( SB_PING_X_BIG - 5 ) - ( SB_TIME_X_BIG - 3 ), 15, colorTable[CT_DKBLUE1]); //RPG-X: Time Bar //34 + CG_FillRect( SB_PING_X_BIG - 3, y+5, 602 - ( SB_PING_X_BIG - 3 ), 15, colorTable[CT_DKBLUE1]); //RPG-X: Ping Bar //35 + + //if ( inIntermission ) +// { + // UI_DrawProportionalString( SB_BOTICON_X+26, SB_HEADERTEXT, ingame_text[IGT_READY],UI_TINYFONT, colorTable[CT_BLACK] ); + // UI_DrawProportionalString( SB_NAME_X, SB_HEADERTEXT, ingame_text[IGT_SB_NAME],UI_TINYFONT, colorTable[CT_BLACK] ); + // UI_DrawProportionalString( SB_SCORE_X, SB_HEADERTEXT, ingame_text[IGT_SB_SCORE],UI_TINYFONT, colorTable[CT_BLACK] ); + // UI_DrawProportionalString( SB_TIME_X, SB_HEADERTEXT, ingame_text[IGT_SB_TIME],UI_TINYFONT, colorTable[CT_BLACK] ); + // UI_DrawProportionalString( SB_PING_X, SB_HEADERTEXT, ingame_text[IGT_SB_PING],UI_TINYFONT, colorTable[CT_BLACK] ); + // UI_DrawProportionalString( SB_KILLEDCNT_X, SB_HEADERTEXT,ingame_text[IGT_TITLEELIMINATED],UI_TINYFONT, colorTable[CT_BLACK] ); + // UI_DrawProportionalString( SB_WORSTENEMY_X, SB_HEADERTEXT,ingame_text[IGT_WORSTENEMY],UI_TINYFONT, colorTable[CT_BLACK] ); + // UI_DrawProportionalString( SB_FAVEWEAPON_X, SB_HEADERTEXT,ingame_text[IGT_FAVORITEWEAPON],UI_TINYFONT, colorTable[CT_BLACK] ); + //} + //else + //{ + //RPG-X: RedTechie (Rank BookMark) + UI_DrawProportionalString( SB_RANK_X_BIG, SB_HEADERTEXT, ingame_text[IGT_SB_RANK], UI_TINYFONT | UI_LEFT, colorTable[CT_BLACK] ); + UI_DrawProportionalString( SB_RPGCLASS_X_BIG, SB_HEADERTEXT, ingame_text[IGT_SB_RPGCLASS],UI_TINYFONT | UI_LEFT, colorTable[CT_BLACK] ); + UI_DrawProportionalString( SB_NAME_X_BIG, SB_HEADERTEXT, ingame_text[IGT_SB_NAME], UI_TINYFONT | UI_LEFT, colorTable[CT_BLACK] ); + if ( cgs.locations ) { + UI_DrawProportionalString( SB_LOC_X_BIG, SB_HEADERTEXT, ingame_text[IGT_SB_LOC], UI_TINYFONT | UI_LEFT, colorTable[CT_BLACK] ); + } + UI_DrawProportionalString( SB_SCORE_X_BIG, SB_HEADERTEXT, ingame_text[IGT_SB_SCORE], UI_TINYFONT | UI_LEFT, colorTable[CT_BLACK] ); + UI_DrawProportionalString( SB_TIME_X_BIG, SB_HEADERTEXT, ingame_text[IGT_SB_TIME], UI_TINYFONT | UI_LEFT, colorTable[CT_BLACK] ); + UI_DrawProportionalString( SB_PING_X_BIG, SB_HEADERTEXT, ingame_text[IGT_SB_PING], UI_TINYFONT | UI_LEFT, colorTable[CT_BLACK] ); + //} + + //trap_R_SetColor( colorTable[CT_DKORANGE] ); + //CG_DrawPic( 605, y, 16, 32, cgs.media.scoreboardEndcap ); + + /*trap_R_SetColor( colorTable[CT_DKBLUE1] ); + CG_DrawPic( SCOREBOARD_X-15, y-28, 25, 28, cgs.media.scoreboardtopleft ); //RPG-X: - RedTechie Top Left + CG_DrawPic( SB_TOPLINE_LENGTH+43, y-28, 25, 28, cgs.media.scoreboardtopright ); //RPG-X: - RedTechie Top Right + CG_DrawPic( SCOREBOARD_X-15, y+325, 36, 28, cgs.media.scoreboardbotleft ); //RPG-X: - RedTechie Bottom Left + CG_DrawPic( SB_TOPLINE_LENGTH+32, y+321, 36, 32, cgs.media.scoreboardbotright ); //RPG-X: - RedTechie Bottom Right +*/ + + y = SB_TOP; + + // SET UP SIZES + //-------------- + if ( (inIntermission) || + (cg.numScores>SB_MAXCLIENTS_BIG) ) + {// POSTGAME + maxClients = SB_MAXCLIENTS_NORMAL; + lineHeight = SB_NORMAL_HEIGHT; + topBorderSize = 16; + bottomBorderSize = 16; + } + else + {// INGAME + maxClients = SB_MAXCLIENTS_BIG; + lineHeight = SB_NORMAL_HEIGHT_BIG; + topBorderSize = 16; + bottomBorderSize = 16; + } + + localClient = qfalse; + + //TiM + /*if ( cgs.gametype >= GT_TEAM ) + { + y-=6; + // TEAM PLAY + //---------- + if ( cg.teamScores[0] >= cg.teamScores[1] ) + { + tTeam = TEAM_RED; + bTeam = TEAM_BLUE; + } + else + { + tTeam = TEAM_BLUE; + bTeam = TEAM_RED; + } + + // TOP TEAM + n1 = CG_GetTeamCount(tTeam, maxClients); + CG_DrawTeamBackground( SCOREBOARD_X+15, y, SB_TOPLINE_LENGTH - 14, n1*lineHeight, 0.33, tTeam, qtrue ); + CG_TeamScoreboard( y, tTeam, fade, maxClients, lineHeight ); + + if(tTeam==TEAM_BLUE) + CG_FillRect( SCOREBOARD_X+12, y, 2, (n1*lineHeight), colorTable[CT_BLUE]); + else + CG_FillRect( SCOREBOARD_X+12, y, 2, (n1*lineHeight), colorTable[CT_RED]); + y += (n1*lineHeight); + maxClients -= n1; + + // BOTTOM TEAM + n2 = CG_GetTeamCount(bTeam, maxClients); + CG_DrawTeamBackground( SCOREBOARD_X+15, y, SB_TOPLINE_LENGTH - 14, n2*lineHeight, 0.33, bTeam, qtrue ); + CG_TeamScoreboard( y, bTeam, fade, maxClients, lineHeight ); + + if(bTeam==TEAM_BLUE) + CG_FillRect( SCOREBOARD_X+12, y, 2, (n2*lineHeight), colorTable[CT_BLUE]); + else + CG_FillRect( SCOREBOARD_X+12, y, 2, (n2*lineHeight), colorTable[CT_RED]); + y += (n2*lineHeight); + maxClients -= n2; + + // SPECTATOR TEAM + n3 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients, lineHeight ); + y += (n3 * lineHeight); + maxClients -= n3; + } + else + {*/ + // FREE FOR ALL + //------------- + n1 = CG_TeamScoreboard( y, TEAM_FREE, fade, maxClients, lineHeight ); + y += (n1 * lineHeight); + maxClients -= n1; + + n2 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients, lineHeight ); + y += (n2 * lineHeight); + maxClients -= n2; + //} + + //LOCAL CLIENT AT BOTTOM OF SCOREBOARD + //------------------------------------ + // + // If we didn't draw the local client in the scoreboard (because ranked lower than maxClients) + // Look for him and display him at the bottom + // + // BUT not for 'GT_SINGLE_PLAYER' (shouldn't happen anyway, right?) + // + if (cgs.gametype==GT_SINGLE_PLAYER) + localClient = qtrue; + + i=0; + while (!localClient && ips.clientNum ) // it's me!! + { + CG_DrawClientScore( y, &cg.scores[i], colorYellow, 0.0, lineHeight==SB_NORMAL_HEIGHT ); + y += lineHeight; + localClient = qtrue; + } + i++; + } + + // Bottom of scoreboard + //trap_R_SetColor( colorTable[CT_DKORANGE] ); + //CG_DrawPic( 13, y-3, 16, 32, cgs.media.corner_12_24 ); // Corner + //RPG-X BookMark Bottum bar + CG_FillRect( SCOREBOARD_X, SB_RPG_X_FIXHEIGHT+60, SB_TOPLINE_LENGTH, 15, colorTable[CT_DKBLUE1]); + //trap_R_SetColor( colorTable[CT_DKORANGE] ); + //CG_DrawPic( 605, y, 16, 32, cgs.media.scoreboardEndcap ); + + //RPG-X: - RedTechie no PRESS FIRE TO CONTINUE! + /*if ( inIntermission ) + { + if ( cgs.gametype != GT_SINGLE_PLAYER) + { + static int flashTime = 0; + static int flashColor = CT_RED; + if ( cg.time - flashTime >= 500 ) + { + flashTime = cg.time; + if ( flashColor == CT_RED ) + { + flashColor = CT_BLACK; + } + else + { + flashColor = CT_RED; + } + } + UI_DrawProportionalString( SB_TOPLINE_LENGTH, y+10+BIGCHAR_HEIGHT, ingame_text[IGT_CLICK_PLAY_AGAIN], UI_RIGHT, colorTable[flashColor] ); + } + }*/ + + //if (cgs.gametype < GT_TEAM) + s = va("%s: %i", ingame_text[IGT_PLAYERS], cg.numScores); // Number of Players + //} + //RPG-X: - RedTechie Dont need extra crap + //TiM: Yeah.... crap suxx. ;P + /*else + { + n1=n2=n3=0; + + for (i=0; i 10) ) + CG_LoadDeferredPlayers(); + + return qtrue; +} + +//================================================================================ + +/* +================ +CG_CenterGiantLine +================ +*/ +static void CG_CenterGiantLine( float y, const char *string ) { + float x; + vec4_t color; + + color[0] = 1; + color[1] = 1; + color[2] = 1; + color[3] = 1; + +// x = 0.5 * ( 640 - GIANT_WIDTH * CG_DrawStrlen( string ) ); +// CG_DrawStringExt( x, y, string, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + + x = 0.5 * ( 640 - UI_ProportionalStringWidth(string,UI_BIGFONT) ); + UI_DrawProportionalString( x, y, string, UI_BIGFONT|UI_DROPSHADOW, color); + +} + +/* +================= +CG_DrawTourneyScoreboard + +Draw the oversize scoreboard for tournements +================= +*/ +void CG_DrawTourneyScoreboard( void ) { + const char *s; + vec4_t color; + int min, tens, ones; + clientInfo_t *ci; + int y; + int i,w; + + // request more scores regularly + if ( cg.scoresRequestTime + 2000 < cg.time ) { + cg.scoresRequestTime = cg.time; + trap_SendClientCommand( "score" ); + } + + color[0] = 1; + color[1] = 1; + color[2] = 1; + color[3] = 1; + + // draw the dialog background + color[0] = color[1] = color[2] = 0; + color[3] = 1; + CG_FillRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, color ); + + // print the mesage of the day + s = CG_ConfigString( CS_MOTD ); + if ( !s[0] ) { + s = "Scoreboard"; + } + + // print optional title + CG_CenterGiantLine( 8, s ); + + // print server time + ones = cg.time / 1000; + min = ones / 60; + ones %= 60; + tens = ones / 10; + ones %= 10; + s = va("%i:%i%i", min, tens, ones ); + + CG_CenterGiantLine( 64, s ); + + + // print the two scores + + y = 160; + if ( cgs.gametype >= GT_TEAM ) { + // + // teamplay scoreboard + // +// CG_DrawStringExt( 8, y, ingame_text[IGT_REDTEAM], color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + UI_DrawProportionalString( 8, y, ingame_text[IGT_REDTEAM], UI_BIGFONT|UI_DROPSHADOW, color); + s = va("%i", cg.teamScores[0] ); +// CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + w = UI_ProportionalStringWidth(s,UI_BIGFONT); + UI_DrawProportionalString(632 - w, y, s, UI_BIGFONT|UI_DROPSHADOW, color); + + y += 64; + +// CG_DrawStringExt( 8, y, ingame_text[IGT_BLUETEAM], color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + UI_DrawProportionalString(8, y, ingame_text[IGT_BLUETEAM], UI_BIGFONT|UI_DROPSHADOW, color); + s = va("%i", cg.teamScores[1] ); +// CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + w = UI_ProportionalStringWidth(s,UI_BIGFONT); + UI_DrawProportionalString(632 - w, y, s, UI_BIGFONT|UI_DROPSHADOW, color); + } else { + // + // free for all scoreboard + // + for ( i = 0 ; i < MAX_CLIENTS ; i++ ) { + ci = &cgs.clientinfo[i]; + if ( !ci->infoValid ) { + continue; + } + if ( ci->team != TEAM_FREE ) { + continue; + } + +// CG_DrawStringExt( 8, y, ci->name, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + UI_DrawProportionalString( 8, y, ci->name, UI_BIGFONT|UI_DROPSHADOW, color); + s = va("%i", ci->score ); +// CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + w = UI_ProportionalStringWidth(s,UI_BIGFONT); + UI_DrawProportionalString(632 - w, y, s, UI_BIGFONT|UI_DROPSHADOW, color); + + y += 64; + } + } + + +} + + + + + + + + +/* +============================================================================= + +AWARDS PRESENTATION + + The following code was ripped from ui_sppostgame.c and has been modified + to show the awards presentation for all game types + +FROM ????Server???? + AW_SPPostgameMenu_f() + -Parse award information and store in struct + + +FROM DrawScoreboard() + AW_Draw() + -PHASE I + Play Sounds + + -PHASE II + AW_DrawAwardsPresentation(timer) + AW_DrawAwards(max) + + -PHASE III + AW_DrawAwards(ALL) + + -PHASE IV + Draw the New Scoreboard + + +============================================================================= +*/ +#define AWARD_PRESENTATION_TIME 2000 // time each medal is shown + +typedef struct { + + int phase; + int ignoreKeysTime; + int starttime; + int scoreboardtime; + int serverId; + + int playerGameRank; + qboolean playersTeamWon; + int playerTied; + + char placeNames[3][64]; + char winTeamText[64]; + char losTeamText[64]; + char winTeamMVPText[64]; + char nameOfMVP[64]; + int scoreOfMVP; + int totalCaptures; + int totalPoints; + int losCaptures; + int losPoints; + int secondPlaceTied; + + int level; + int numClients; + int won; + int numAwards; + int awardsEarned[9]; + int awardsLevels[9]; + qboolean playedSound[9]; + int lastTier; + sfxHandle_t winnerSound; + int winnerDelay; + sfxHandle_t youPlacedSound; + sfxHandle_t commendationsSound; + + vec3_t intermission_origin; + vec3_t intermission_angle; +} postgameMenuInfo_t; + +static postgameMenuInfo_t postgameMenuInfo; +static qboolean postGameMenuStructInited = qfalse; + +void InitPostGameMenuStruct() +{ + if (qfalse == postGameMenuStructInited) + { + memset( &postgameMenuInfo, 0, sizeof(postgameMenuInfo) ); + postGameMenuStructInited = qtrue; + } +} + +/* +char *cg_medalNames[AWARD_MAX] = +{ + "Efficiency", // AWARD_EFFICIENCY, Accuracy + "Sharpshooter", // AWARD_SHARPSHOOTER, Most compression rifle frags + "Untouchable", // AWARD_UNTOUCHABLE, Perfect (no deaths) + "Logistics", // AWARD_LOGISTICS, Most pickups + "Tactician", // AWARD_TACTICIAN, Kills with all weapons + "Demolitionist", // AWARD_DEMOLITIONIST, Most explosive damage kills + "Streak", // AWARD_STREAK, Ace/Expert/Master/Champion + "Role", // AWARD_TEAM, MVP/Defender/Warrior/Carrier/Interceptor/Bravery + "Section 31" // AWARD_SECTION31 All-around god +}; +*/ + +int cg_medalNames[AWARD_MAX] = +{ + IGT_EFFICIENCY, // AWARD_EFFICIENCY, Accuracy + IGT_SHARPSHOOTER, // AWARD_SHARPSHOOTER, Most compression rifle frags + IGT_UNTOUCHABLE, // AWARD_UNTOUCHABLE, Perfect (no deaths) + IGT_LOGISTICS, // AWARD_LOGISTICS, Most pickups + IGT_TACTICIAN, // AWARD_TACTICIAN, Kills with all weapons + IGT_DEMOLITIONIST, // AWARD_DEMOLITIONIST, Most explosive damage kills + IGT_ROLE, // AWARD_STREAK, Ace/Expert/Master/Champion + IGT_STREAK, // AWARD_TEAM, MVP/Defender/Warrior/Carrier/Interceptor/Bravery + IGT_SECTION31 // AWARD_SECTION31 All-around god +}; + + +char *cg_medalPicNames[AWARD_MAX] = { + "menu/medals/medal_efficiency", // AWARD_EFFICIENCY, + "menu/medals/medal_sharpshooter", // AWARD_SHARPSHOOTER, + "menu/medals/medal_untouchable", // AWARD_UNTOUCHABLE, + "menu/medals/medal_logistics", // AWARD_LOGISTICS, + "menu/medals/medal_tactician", // AWARD_TACTICIAN, + "menu/medals/medal_demolitionist", // AWARD_DEMOLITIONIST, + "menu/medals/medal_ace", // AWARD_STREAK, + "menu/medals/medal_teammvp", // AWARD_TEAM, + "menu/medals/medal_section31" // AWARD_SECTION31 +}; + +char *cg_medalSounds[AWARD_MAX] = { + "sound/voice/computer/misc/effic.wav", // AWARD_EFFICIENCY, + "sound/voice/computer/misc/sharp.wav", // AWARD_SHARPSHOOTER, + "sound/voice/computer/misc/untouch.wav", // AWARD_UNTOUCHABLE, + "sound/voice/computer/misc/log.wav", // AWARD_LOGISTICS, + "sound/voice/computer/misc/tact.wav", // AWARD_TACTICIAN, + "sound/voice/computer/misc/demo.wav", // AWARD_DEMOLITIONIST, + "sound/voice/computer/misc/ace.wav", // AWARD_STREAK, + "",/*shouldn't use. use cg_medalTeamSounds instead*/ // AWARD_TEAM, + "sound/voice/computer/misc/sec31.wav" // AWARD_SECTION31 +}; + +char *cg_medalTeamPicNames[TEAM_MAX] = { + "", // TEAM_NONE + "menu/medals/medal_teammvp", // TEAM_MVP, + "menu/medals/medal_teamdefender", // TEAM_DEFENDER, + "menu/medals/medal_teamwarrior", // TEAM_WARRIOR, + "menu/medals/medal_teamcarrier", // TEAM_CARRIER, + "menu/medals/medal_teaminterceptor",// TEAM_INTERCEPTOR, + "menu/medals/medal_teambravery" // TEAM_BRAVERY, +}; + +char *cg_medalTeamSounds[TEAM_MAX] = { + "", // TEAM_NONE + "sound/voice/computer/misc/mvp.wav", // TEAM_MVP, + "sound/voice/computer/misc/defender.wav", // TEAM_DEFENDER, + "sound/voice/computer/misc/warrior.wav", // TEAM_WARRIOR, + "sound/voice/computer/misc/carrier.wav", // TEAM_CARRIER, + "sound/voice/computer/misc/intercept.wav", // TEAM_INTERCEPTOR, + "sound/voice/computer/misc/bravery.wav" // TEAM_BRAVERY, +}; + + +char *cg_medalStreakPicNames[AWARD_STREAK_MAX] = { + "", // AWARD_NONE + "menu/medals/medal_ace", // AWARD_STREAK_ACE, + "menu/medals/medal_expert", // AWARD_STREAK_EXPERT, + "menu/medals/medal_master", // AWARD_STREAK_MASTER, + "menu/medals/medal_champion" // AWARD_STREAK_CHAMPION, +}; + +char *cg_medalStreakSounds[AWARD_STREAK_MAX] = { + "", // AWARD_NONE + "sound/voice/computer/misc/ace.wav", // AWARD_STREAK_ACE, + "sound/voice/computer/misc/expert.wav", // AWARD_STREAK_EXPERT, + "sound/voice/computer/misc/master.wav", // AWARD_STREAK_MASTER, + "sound/voice/computer/misc/champion.wav" // AWARD_STREAK_CHAMPION, +}; +/* +char *cg_medalStreakNames[AWARD_STREAK_MAX] = { + "", // AWARD_NONE + "Ace", // AWARD_STREAK_ACE, + "Expert", // AWARD_STREAK_EXPERT, + "Master", // AWARD_STREAK_MASTER, + "Champion" // AWARD_STREAK_CHAMPION, +}; +*/ + +int cg_medalStreakNames[AWARD_STREAK_MAX] = { + IGT_NONE, // AWARD_NONE + IGT_ACE, // AWARD_STREAK_ACE, + IGT_EXPERT, // AWARD_STREAK_EXPERT, + IGT_MASTER, // AWARD_STREAK_MASTER, + IGT_CHAMPION // AWARD_STREAK_CHAMPION, +}; + +int cg_medalTeamNames[TEAM_MAX] = { + IGT_NONE, // AWARD_NONE + IGT_MVP, // TEAM_MVP, + IGT_DEFENDER, // TEAM_DEFENDER, + IGT_WARRIOR, // TEAM_WARRIOR, + IGT_CARRIER, // TEAM_CARRIER, + IGT_INTERCEPTOR, // TEAM_INTERCEPTOR + IGT_BRAVERY // TEAM_BRAVERY +}; + + +// spaced by 70 pixels apart +static int medalLocations[6] = {570, 500, 430, 360, 290}; + +static void AW_DrawMedal( int medal, int amount, int x, int y ) +{ + char buf[20]; + qhandle_t hShader; + vec4_t yellow; + + // RED GREEN BLUE ALPHA + //----------------------------------------------------------------------- + yellow[0]=1.0f; yellow[1]=1.0f; yellow[2]=0.5f; yellow[3]=1.0f; + + if (medal == AWARD_STREAK) { + hShader = trap_R_RegisterShaderNoMip( cg_medalStreakPicNames[amount/5] ); + } + else if (medal == AWARD_TEAM) { + hShader = trap_R_RegisterShaderNoMip( cg_medalTeamPicNames[amount] ); + } + else { + hShader = trap_R_RegisterShaderNoMip( cg_medalPicNames[medal] ); + } + CG_DrawPic( x, y, 48, 48, hShader); + + if (postgameMenuInfo.phase<4) + { + //--------------------------------------------------------------- NAME OF THE AWARD + Com_sprintf( buf, sizeof(buf), "%s", ingame_text[cg_medalNames[medal]]); + if (medal== AWARD_STREAK) + Com_sprintf( buf, sizeof(buf), "%s", ingame_text[cg_medalStreakNames[amount/5]]); + if (medal== AWARD_TEAM) + Com_sprintf( buf, sizeof(buf), "%s", ingame_text[cg_medalTeamNames[amount]]); + +// Q_CleanStr( buf ); + + UI_DrawProportionalString( x+24, y+50, buf, UI_TINYFONT|UI_CENTER, yellow ); + //--------------------------------------------------------------- NAME OF THE AWARD + } +} + +//======================== +// DrawTheMedals(int max) +// +// This function cycles from 0 to max and calls some functions to Draw the Picture and +// the text below for efficency. +// +//======================== +//RPG-X: RedTechie - No awards +/*static int presentedTeamAward[TEAM_MAX]; +static void AW_DrawTheMedals( int max ) { + int n, i; + int medal; + int amount; + int x, y; + int extraAwardOffset = 0; + + for( n = 0; n < max; n++ ) { + x = medalLocations[n] - extraAwardOffset; + y = AWARD_Y; + medal = postgameMenuInfo.awardsEarned[n]; + amount = postgameMenuInfo.awardsLevels[n]; + + if (medal == AWARD_TEAM) + {//amount is going to be a bitflag field... + for( i = TEAM_MVP; i < TEAM_MAX; i++ ) + { + if ( amount & (1<= postgameMenuInfo.numAwards ) + { + awardNum = (postgameMenuInfo.numAwards-1); + } + + medal = postgameMenuInfo.awardsEarned[awardNum]; + amount = postgameMenuInfo.awardsLevels[awardNum]; + + if ( medal == AWARD_TEAM ) + {//amount is going to be a bitflag field... + if ( !allTeamAwardsAnnounced ) + { + for( i = TEAM_MVP; i < TEAM_MAX; i++ ) + { + if ( amount & (1< cg.time ) + {//already announced this award + AW_PresentMedal( AWARD_TEAM, i, timer, awardNum ); + } + teamAward++; + } + } + } + } + else + { + AW_PresentMedal( medal, amount, timer, awardNum ); + } + + AW_DrawTheMedals( awardNum + 1 ); // Draw the awards + + if ( !awardNextDebounceTime ) + { + awardNextDebounceTime = cg.time + AWARD_PRESENTATION_TIME; + } + else if ( awardNextDebounceTime <= cg.time ) + { + awardNextDebounceTime = cg.time + AWARD_PRESENTATION_TIME; + if ( i == TEAM_MAX ) + { + //finished loop? Then we announced all these + allTeamAwardsAnnounced = qtrue; + } + if ( !extraAwardTime || allTeamAwardsAnnounced ) + { + awardNum++; + } + else + { + nextTeamAward++; + } + } +} +*/ + +/* +================= +AW_Draw +================= +*/ +static qboolean AW_Draw( void ) +{ + char buf[64]; + int timer; + int y=0, yfrom=0; + int len; + vec4_t white, red, blue, yellow, blueA; // new colors + + // RED GREEN BLUE ALPHA + //----------------------------------------------------------------------- + white[0] = white[1] = white[2] = 1.0f; white[3] = 1.0f; + red[0] = 1.0f; red[1] = 0.5f; red[2] = 0.5f; red[3] = 1.0f; + blue[0] = 0.5f; blue[1] = 0.5f; blue[2] = 1.0f; blue[3] = 1.0f; + yellow[0]= 1.0f; yellow[1]= 1.0f; yellow[2]= 0.5f; yellow[3]= 1.0f; + blueA[0] = 0.367f; blueA[1] = 0.261f;blueA[2] = 0.722f; blueA[3] = 0.5f; + + // REASONS NOT TO SHOW THE AWARDS CEREMONY: + //-------------------------------------------- + // if scoreboardtime is not set, we set it to current time plus five seconds slop time + if (postgameMenuInfo.scoreboardtime == 0) + { + postgameMenuInfo.scoreboardtime = cg.time+5000; + return qfalse; + } + + // if scoreboardtime is greater than current time, we don't draw anything (including no scoreboard) + // This should only happen if the scoreboard time had not been set properly to current time by + // the setup function below + if (postgameMenuInfo.scoreboardtime > cg.time+10) + return qfalse; + + // OK, so we've given the slop time and a chance for the information to be set properly in + // case of network lag. if .starttime is still not set properly, we have no right to show + // anything in the awards ceremony and go straight to the spiffy scoreboard... + if (postgameMenuInfo.starttime==0) + return qtrue; + + + + + + //RPG-X: RedTechie Dont need this aswell this also prints out what team your on and if your winning just takes up to much room + // ALL PHASES + // Display Your Rank / Your Team Wins + //----------------------------------------------- + /*if (cg.snap->ps.persistant[PERS_TEAM]!=TEAM_SPECTATOR) + { + if (cgs.gametype <= GT_SINGLE_PLAYER ) + { + if (postgameMenuInfo.playerTied) + Com_sprintf( buf, sizeof(buf), "%s %i", ingame_text[IGT_TIED_FOR], postgameMenuInfo.playerTied); + else + Com_sprintf( buf, sizeof(buf), "%s %i", ingame_text[IGT_YOUR_RANK], postgameMenuInfo.playerGameRank+1); + UI_DrawProportionalString( 14, AWARD_Y, buf, UI_BIGFONT|UI_LEFT, white ); + } + else + { + char *teamName = NULL; + + switch ( cg.snap->ps.persistant[PERS_TEAM] ) + { + case TEAM_RED: + teamName = (char *)CG_ConfigString( CS_RED_GROUP ); + break; + case TEAM_BLUE: + teamName = (char *)CG_ConfigString( CS_BLUE_GROUP ); + break; + } + if ( teamName == NULL || teamName[0] == 0 ) + { + teamName = ingame_text[IGT_YOUR_TEAM]; + } + + if (postgameMenuInfo.playersTeamWon) + Com_sprintf( buf, sizeof(buf), "%s %s", teamName, ingame_text[IGT_WON]); + else + Com_sprintf( buf, sizeof(buf), "%s %s", teamName, ingame_text[IGT_LOST]); + if (postgameMenuInfo.playerGameRank == 2) + Com_sprintf( buf, sizeof(buf), "%s", ingame_text[IGT_TEAMS_TIED]); + UI_DrawProportionalString( 14, AWARD_Y, buf, UI_BIGFONT|UI_LEFT, white ); + + } + }*/ + + // PHASES I & II & III + // Draw Character / Team names by podiums + //----------------------------------------------- + if (postgameMenuInfo.phase < 4) + { + if (cgs.gametype <= GT_SINGLE_PLAYER ) + { + // NON TEAM GAMES + // We want the top three player's names and their ranks below the podium + if ( postgameMenuInfo.numClients > 2 ) + { + UI_DrawProportionalString( 510, 480 - 64 - PROP_HEIGHT, postgameMenuInfo.placeNames[2], UI_CENTER, white ); + if (postgameMenuInfo.secondPlaceTied) + UI_DrawProportionalString( 510, 480 - 38 - PROP_HEIGHT, ingame_text[IGT_2ND], UI_CENTER, yellow ); + else + UI_DrawProportionalString( 510, 480 - 38 - PROP_HEIGHT, ingame_text[IGT_3RD], UI_CENTER, yellow ); + } + if ( postgameMenuInfo.numClients > 1) + { + UI_DrawProportionalString( 130, 480 - 64 - PROP_HEIGHT, postgameMenuInfo.placeNames[1], UI_CENTER, white ); + UI_DrawProportionalString( 130, 480 - 38 - PROP_HEIGHT, ingame_text[IGT_2ND], UI_CENTER, yellow ); + } + + UI_DrawProportionalString( 320, 480 - 64 - 2 * PROP_HEIGHT, postgameMenuInfo.placeNames[0], UI_CENTER, white ); + UI_DrawProportionalString( 320, 480 - 38 - 2 * PROP_HEIGHT, ingame_text[IGT_1ST], UI_CENTER, yellow ); + } + else //if (cg.snap->ps.persistant[PERS_TEAM]!=TEAM_SPECTATOR) + { + // TEAM + // We want the winning team character's name, a line explaining "Klingon MVP" + UI_DrawProportionalString( 320, 480 - 64 - 2 * PROP_HEIGHT, postgameMenuInfo.nameOfMVP, UI_CENTER, white ); + UI_DrawProportionalString( 320, 480 - 34 - 2 * PROP_HEIGHT, postgameMenuInfo.winTeamMVPText, UI_CENTER, yellow ); + Com_sprintf( buf, sizeof(buf), "%s %i", ingame_text[IGT_POINTS], postgameMenuInfo.scoreOfMVP); + UI_DrawProportionalString( 320, 480 - 14 - 2 * PROP_HEIGHT, buf, UI_CENTER, yellow ); + + + + // SPECIAL TEAM STATS BAR ON RIGHT SIDE OF SCREEN + //----------------------------------------------- + // + // THE TOP BAR + y=130; + if (cgs.gametype == GT_CTF) + Com_sprintf( buf, sizeof(buf), ingame_text[IGT_GAME_CAPTUREFLAG] ); + else + Com_sprintf( buf, sizeof(buf), ingame_text[IGT_GAME_TEAMHOLOMATCH] ); + len = UI_ProportionalStringWidth( buf, UI_SMALLFONT ); + + trap_R_SetColor( colorTable[CT_DKORANGE] ); + CG_DrawPic( 640-32, y+20, 16, -32, cgs.media.corner_8_16_b ); + CG_FillRect(640-80, y+1, 48, 15, colorTable[CT_DKORANGE]); + UI_DrawProportionalString( 640-82, y, buf, UI_RIGHT, colorTable[CT_DKORANGE] ); + trap_R_SetColor( colorTable[CT_DKORANGE] ); + CG_DrawPic( 640-80-len-5, y+1, -14, 20, cgs.media.scoreboardEndcap ); + yfrom = y+20; + y+=20; + + Com_sprintf( buf, sizeof(buf), "%s", postgameMenuInfo.winTeamText); + UI_DrawProportionalString( 640-34, y, buf, UI_BIGFONT|UI_RIGHT, yellow ); + y+=35; + + if (cgs.gametype == GT_CTF) + { + // HOW MANY CAPUTRES??? + Com_sprintf( buf, sizeof(buf), "%s %i", ingame_text[IGT_CAPTURES], postgameMenuInfo.totalCaptures); + UI_DrawProportionalString( 640-34, y, buf, UI_RIGHT, white ); + y+=20; + } + + // AND THE POINTS??? + Com_sprintf( buf, sizeof(buf), "%s %i", ingame_text[IGT_POINTS], postgameMenuInfo.totalPoints); + UI_DrawProportionalString( 640-34, y, buf, UI_RIGHT, white ); + y+=30; + + // THE SIDE BAR + CG_FillRect( 640-16-12, yfrom, 8, y-yfrom, colorTable[CT_DKORANGE]); + + + // THE MIDDLE BARS + CG_FillRect( 640-115, y, 95, 20, colorTable[CT_DKORANGE]); + y+=3; + if (postgameMenuInfo.playerGameRank==2) + Com_sprintf( buf, sizeof(buf), "%s", ingame_text[IGT_TEAMS_TIED]); + else + Com_sprintf( buf, sizeof(buf), "%s", ingame_text[IGT_VICTOR]); + + UI_DrawProportionalString( 640-34, y, buf, UI_RIGHT, colorTable[CT_BLACK] ); + y+=20; + + y+=30; + + yfrom=y; + CG_FillRect( 640-115, y, 95, 20, colorTable[CT_DKORANGE]); + y+=3; + if (postgameMenuInfo.playerGameRank==2) + Com_sprintf( buf, sizeof(buf), "%s", ingame_text[IGT_TEAMS_TIED]); + else + Com_sprintf( buf, sizeof(buf), "%s", ingame_text[IGT_DEFEATED]); + + UI_DrawProportionalString( 640-34, y, buf, UI_RIGHT, colorTable[CT_BLACK] ); + y+=25; + + // LOSING TEAM NAME + Com_sprintf( buf, sizeof(buf), "%s", postgameMenuInfo.losTeamText); + UI_DrawProportionalString( 640-34, y, buf, UI_BIGFONT|UI_RIGHT, yellow ); + y+=35; + + if (cgs.gametype == GT_CTF) + { + // HOW MANY CAPUTRES??? + Com_sprintf( buf, sizeof(buf), "%s %i", ingame_text[IGT_CAPTURES], postgameMenuInfo.losCaptures); + UI_DrawProportionalString( 640-34, y, buf, UI_RIGHT, white ); + y+=20; + } + + // AND THE POINTS??? + Com_sprintf( buf, sizeof(buf), "%s %i", ingame_text[IGT_POINTS], postgameMenuInfo.losPoints); + UI_DrawProportionalString( 640-34, y, buf, UI_RIGHT, white ); + y+=20; + + // THE SIDE BAR + CG_FillRect( 640-16-12, yfrom, 8, y-yfrom, colorTable[CT_DKORANGE]); + + // THE BOTTOM BAR + trap_R_SetColor( colorTable[CT_DKORANGE] ); + CG_DrawPic( 640-32, y, 16, 32, cgs.media.corner_8_16_b ); + CG_FillRect(640-100, y+4, 68, 15, colorTable[CT_DKORANGE]); + trap_R_SetColor( colorTable[CT_DKORANGE] ); + CG_DrawPic( 640-100-2, y+4, -14, 20, cgs.media.scoreboardEndcap ); + yfrom = y+20; + y+=20; + } + } + + + // if we are arriving as a spectator, there are no awards for you (not even sure that spectators + // will get the awards string that sets the whole thing up...) +// if (cg.snap->ps.persistant[PERS_TEAM]==TEAM_SPECTATOR) +// return qtrue; + + + // PHASE I + // Play Winner sound + //----------------------------------------------- + if( postgameMenuInfo.phase == 1 ) + { + timer = cg.time - postgameMenuInfo.starttime; // set up the timer + + //RPG-X: RedTechie - No winner + /*if( postgameMenuInfo.winnerSound ) // if havn't played yet + { + trap_S_StartLocalSound( postgameMenuInfo.winnerSound, CHAN_ANNOUNCER ); // play the sound + postgameMenuInfo.winnerSound = 0; // don't play again + // force another wait since we played a sound here + postgameMenuInfo.starttime = cg.time + postgameMenuInfo.winnerDelay; + timer = 0; + }*/ + + if( timer < 2000 ) + { + return qfalse; + } + + //RPG-X: RedTechie - No ranking sound + /*if (cg.snap->ps.persistant[PERS_TEAM]!=TEAM_SPECTATOR) + { + if (postgameMenuInfo.youPlacedSound) + { + trap_S_StartLocalSound( postgameMenuInfo.youPlacedSound, CHAN_ANNOUNCER ); + postgameMenuInfo.youPlacedSound = 0; + // force another wait since we played a sound here + postgameMenuInfo.starttime = cg.time; + timer = 0; + } + }*/ + + // if we didn't play a youPlaced sound, this won't slow us up at all + if( timer < 2000 ) + { + return qfalse; + } + + // play "commendations" sound + //RPG-X: RedTechie - No commendations + /*if (postgameMenuInfo.commendationsSound) + { + trap_S_StartLocalSound( postgameMenuInfo.commendationsSound, CHAN_ANNOUNCER ); + postgameMenuInfo.commendationsSound = 0; + // force another wait since we played a sound here + postgameMenuInfo.starttime = cg.time; + }*/ + + if( timer < 2500 ) + { + return qfalse; + } + // After 7 seconds, go on to phase II + postgameMenuInfo.phase = 2; // Setup PHASE II + postgameMenuInfo.starttime = cg.time; // +// memset( presentedTeamAward, 0, sizeof( presentedTeamAward ) ); +// extraAwardTime = 0; +// allTeamAwardsAnnounced = qfalse; +// awardNextDebounceTime = 0; +// awardNum = 0; +// nextTeamAward = 0; + + return qfalse; + } + + // PHASE II + // Introduce each medal on a timer + //----------------------------------------------- + //RPG-X: RedTechie - No awards + if( postgameMenuInfo.phase == 2 ) + { + + timer = cg.time - postgameMenuInfo.starttime;// - extraAwardTime; // Setup Timer + if( timer >= ( postgameMenuInfo.numAwards * AWARD_PRESENTATION_TIME ) ) + { + postgameMenuInfo.phase = 3; + //AW_DrawTheMedals( postgameMenuInfo.numAwards ); // Draw All Medals for this frame + } + else + { + //AW_DrawAwardsPresentation( timer ); // Draw Some Medals + } + + return qfalse; + } + + // PHASE III + // Just draw all the awards medals + //----------------------------------------------- + //RPG-X: RedTechie - No awards + if( postgameMenuInfo.phase == 3 ) + { + //AW_DrawTheMedals( postgameMenuInfo.numAwards ); + timer = cg.time - postgameMenuInfo.starttime; // Setup Timer + if (timer >= 5000) + postgameMenuInfo.phase = 4; // Got To Phase IV after 5 seconds + + return qfalse; + } + + + // PHASE IV + // Draw Medals & Draw the scoreboard + //----------------------------------------------- + //RPG-X: RedTechie - No awards + + postgameMenuInfo.phase = 4; + //AW_DrawTheMedals( postgameMenuInfo.numAwards ); + return qtrue; + +} + + +/* +================= +AW_SPPostgameMenu_Cache +================= + +void AW_SPPostgameMenu_Cache( void ) { + int n; + qboolean buildscript; + + buildscript = trap_Cvar_VariableValue("com_buildscript"); + + for( n = 0; n < AWARD_MAX; n++ ) + { + trap_R_RegisterShaderNoMip( ui_medalPicNames[n] ); + trap_S_RegisterSound( ui_medalSounds[n] ); + } + + if( buildscript ) { + trap_Cmd_ExecuteText( EXEC_APPEND, "music music/win\n" ); + trap_Cmd_ExecuteText( EXEC_APPEND, "music music/loss\n" ); + trap_S_RegisterSound( "sound/player/announce/youwin.wav" ); + } +} +*/ + + +/* +================= +AW_SPPostgameMenu_Cache + + This function writes the winning the winning team name if + we are in a team game. +================= +*/ +static void AW_Prepname( int index) +{ +// int len; + char name[64]; // The winning team's name.. Red/Blue/ Klingon... + char MVPname[64]; + char otherName[64]; + const char *redPtr = NULL; + const char *bluePtr = NULL; + char *red_team; + char *blue_team; + + red_team = ingame_text[IGT_REDTEAM]; + blue_team = ingame_text[IGT_BLUETEAM]; + + + redPtr = CG_ConfigString( CS_RED_GROUP ); + bluePtr= CG_ConfigString( CS_BLUE_GROUP); + if (!Q_stricmp(redPtr, "")) + redPtr = red_team; + + if (!Q_stricmp(bluePtr, "")) + bluePtr = blue_team; + + if (postgameMenuInfo.playerGameRank == 1) // Blue Team Won + { + Com_sprintf(name, sizeof(name), "%s", bluePtr); //Winners + Com_sprintf(otherName, sizeof(otherName), "%s", redPtr); //Losers + Com_sprintf(MVPname, sizeof(name), "%s %s", bluePtr, ingame_text[IGT_MVP]); + } + else if (postgameMenuInfo.playerGameRank == 0) // Red Team Won + { + Com_sprintf(name, sizeof(name), "%s", redPtr); //Winners + Com_sprintf(otherName, sizeof(otherName), "%s", bluePtr); //Losers + Com_sprintf(MVPname, sizeof(name), "%s %s", redPtr, ingame_text[IGT_MVP]); + } + else // Teams Tied + { + Com_sprintf(name, sizeof(name), "%s", ingame_text[IGT_TEAMS_TIED]); + Com_sprintf(otherName, sizeof(otherName), "%s", ingame_text[IGT_TEAMS_TIED]); + Com_sprintf(MVPname, sizeof(name), "%s %s", ingame_text[IGT_OVERALL], ingame_text[IGT_MVP]); + } + + Q_strncpyz( postgameMenuInfo.winTeamText, name, sizeof(postgameMenuInfo.winTeamText) ); + Q_strncpyz( postgameMenuInfo.losTeamText, otherName, sizeof(postgameMenuInfo.losTeamText) ); + Q_strncpyz( postgameMenuInfo.winTeamMVPText, MVPname, sizeof(postgameMenuInfo.winTeamMVPText) ); +} + + +/* +================= +AW_SPPostgameMenu_f + + This function essentially initializes the SPPostgameMenu_t structure + + Is called by the server command "awards ..." +================= +*/ + +void AW_SPPostgameMenu_f( void ) { + int playerGameRank; + int playerClientNum; + int playerTeam; + int n, clNum[3] = {0,0,0}; + int awardFlags; + int numNames; + char temp_string[200]; + + memset( &postgameMenuInfo, 0, sizeof(postgameMenuInfo) ); + + + // IMPORT AWARDS INFORMATION FROM SERVER AND CMD STRING + //___________________________________________________________________________________________ + // + playerClientNum = (cg.snap->ps.clientNum); + playerGameRank = (cg.snap->ps.persistant[PERS_RANK]&~RANK_TIED_FLAG); + //playerTeam = (cg.snap->ps.persistant[PERS_TEAM]); + playerTeam = (cgs.clientinfo[playerClientNum].team);//this should be more accurate + + postgameMenuInfo.starttime = cg.time; // Initialize Timers + postgameMenuInfo.scoreboardtime = cg.time; + postgameMenuInfo.numAwards = 0; // Initialize Awards Count + postgameMenuInfo.playerGameRank = playerGameRank; // Store Player Rank + postgameMenuInfo.numClients = atoi( CG_Argv( 1 ) ); // Store Number Of Clients + postgameMenuInfo.playerTied = (cg.snap->ps.persistant[PERS_RANK] & RANK_TIED_FLAG); + + if (playerTeam == playerGameRank+1 || playerGameRank==2) + postgameMenuInfo.playersTeamWon = qtrue; + else + postgameMenuInfo.playersTeamWon = qfalse; + + + for (n=0; (n>1; + } + + Q_strncpyz( postgameMenuInfo.nameOfMVP, (CG_Argv(numNames + postgameMenuInfo.numAwards + 3)), sizeof(postgameMenuInfo.nameOfMVP) ); + postgameMenuInfo.scoreOfMVP = atoi( CG_Argv(numNames + postgameMenuInfo.numAwards + 4)); + postgameMenuInfo.totalCaptures = atoi( CG_Argv(numNames + postgameMenuInfo.numAwards + 5)); + postgameMenuInfo.totalPoints = atoi( CG_Argv(numNames + postgameMenuInfo.numAwards + 6)); + if (cgs.gametype <= GT_SINGLE_PLAYER) + { + postgameMenuInfo.playerGameRank = atoi( CG_Argv(numNames + postgameMenuInfo.numAwards + 7)); + playerGameRank = postgameMenuInfo.playerGameRank; + } + postgameMenuInfo.playerTied = atoi( CG_Argv(numNames + postgameMenuInfo.numAwards + 8)); + postgameMenuInfo.losCaptures = atoi( CG_Argv(numNames + postgameMenuInfo.numAwards + 9)); + postgameMenuInfo.losPoints = atoi( CG_Argv(numNames + postgameMenuInfo.numAwards + 10)); + postgameMenuInfo.secondPlaceTied = atoi( CG_Argv(numNames + postgameMenuInfo.numAwards + 11)); + VectorSet(postgameMenuInfo.intermission_origin, + atoi( CG_Argv(numNames + postgameMenuInfo.numAwards + 12)), + atoi( CG_Argv(numNames + postgameMenuInfo.numAwards + 13)), + atoi( CG_Argv(numNames + postgameMenuInfo.numAwards + 14))); + VectorSet(postgameMenuInfo.intermission_angle, + atoi( CG_Argv(numNames + postgameMenuInfo.numAwards + 15)), + atoi( CG_Argv(numNames + postgameMenuInfo.numAwards + 16)), + atoi( CG_Argv(numNames + postgameMenuInfo.numAwards + 17))); + + cg.predictedPlayerState.pm_type = PM_INTERMISSION; + VectorCopy(postgameMenuInfo.intermission_origin, cg.predictedPlayerState.origin); + VectorCopy(postgameMenuInfo.intermission_angle, cg.predictedPlayerState.viewangles); + + + // SOUNDS + //___________________________________________________________________________________________ + // + if (postgameMenuInfo.numAwards) + { + postgameMenuInfo.commendationsSound = trap_S_RegisterSound("sound/voice/computer/misc/commendations.wav"); + } + else + { + postgameMenuInfo.commendationsSound = 0; + } + if (cgs.gametype <= GT_SINGLE_PLAYER) // for FFA, TOURNAMENT, and SINGLE_PLAYER TOURNAMENT: + { + if ( playerGameRank != 0 ) + { // Someone else wins + char *skin; + + skin = cgs.clientinfo[clNum[0]].skinName; + + if( Q_stricmp( skin, "default" ) == 0 ) { + skin = cgs.clientinfo[clNum[0]].modelName; + } + if( Q_stricmp( skin, "red" ) == 0 ) { + skin = cgs.clientinfo[clNum[0]].modelName; + } + if( Q_stricmp( skin, "blue" ) == 0 ) { + skin = cgs.clientinfo[clNum[0]].modelName; + } + + postgameMenuInfo.winnerSound = trap_S_RegisterSound( va( "sound/voice/computer/misc/%s_wins.wav", skin ) ); + if (0 == postgameMenuInfo.winnerSound) + { + postgameMenuInfo.winnerSound = trap_S_RegisterSound( va( "sound/voice/computer/misc/progcomp.wav", skin ) ); + } + postgameMenuInfo.winnerDelay = 2500; + if (1 == playerGameRank || postgameMenuInfo.playerTied==2) + { + postgameMenuInfo.youPlacedSound = trap_S_RegisterSound("sound/voice/computer/misc/2nd.wav"); + } + else if (2 == playerGameRank || postgameMenuInfo.playerTied==3) + { + postgameMenuInfo.youPlacedSound = trap_S_RegisterSound("sound/voice/computer/misc/3rd.wav"); + } + else + { + postgameMenuInfo.youPlacedSound = trap_S_RegisterSound("sound/voice/computer/misc/notPlace.wav"); + } + + // You lost, you get to listen to loser music. + // This might be a bit of a downer in FFA, since far more often than not you are not the winner... + // However, in this case the track is NOT a funeral march with an opera singer bellowing "LOSER, LOSER, LOSER, HA HA". + // SOOOOOO for consistency's sake, you will always hear the "loss" track when you don't win. --Pat + trap_S_StartBackgroundTrack( "music/loss", "music/loss" ); + } + else + { // You win + postgameMenuInfo.winnerSound = trap_S_RegisterSound( "sound/voice/computer/misc/youwin.wav" ); + postgameMenuInfo.youPlacedSound = 0; + postgameMenuInfo.winnerDelay = 500; + + // You won, you get to listen to the winner music. + trap_S_StartBackgroundTrack( "music/win", "music/win" ); + } + } + else // for TEAM, and CAPTURE THE FLAG: + { + qboolean bRaceSound = qfalse; + + AW_Prepname( 0); // Set Up "xxxx Team Wins" text + + // THE VOICE + //__________________________________________________ + if (playerGameRank == 1) + { + Com_sprintf(temp_string, sizeof(temp_string), "sound/voice/computer/misc/%s_wins.wav" , CG_ConfigString( CS_BLUE_GROUP )); + + postgameMenuInfo.winnerSound = trap_S_RegisterSound(temp_string); + if (!postgameMenuInfo.winnerSound) + { + postgameMenuInfo.winnerSound = trap_S_RegisterSound( "sound/voice/computer/misc/blueteam_wins.wav" ); + } + else + { + bRaceSound = qtrue; + } + } + else if (playerGameRank == 0) + { + Com_sprintf(temp_string, sizeof(temp_string), "sound/voice/computer/misc/%s_wins.wav" , CG_ConfigString( CS_RED_GROUP )); + + postgameMenuInfo.winnerSound = trap_S_RegisterSound(temp_string); + if (!postgameMenuInfo.winnerSound) + { + postgameMenuInfo.winnerSound = trap_S_RegisterSound( "sound/voice/computer/misc/redteam_wins.wav" ); + } + else + { + bRaceSound = qtrue; + } + } + else if (playerGameRank == 2) + { + postgameMenuInfo.winnerSound = trap_S_RegisterSound( "sound/voice/computer/misc/teamstied.wav" ); + } + if (!bRaceSound && (playerTeam == playerGameRank+1) ) + { + postgameMenuInfo.winnerSound = trap_S_RegisterSound( "sound/voice/computer/misc/yourteam_wins.wav" ); + } + + if (bRaceSound) + { + postgameMenuInfo.winnerDelay = 4000; + } + else + { + postgameMenuInfo.winnerDelay = 1000; + } + + // THE MUSIC + //__________________________________________________ + if (postgameMenuInfo.playersTeamWon) { + trap_S_StartBackgroundTrack( "music/win", "music/win" ); + } else { + trap_S_StartBackgroundTrack( "music/loss", "music/loss" ); + } + } + + //Set To Phase I + postgameMenuInfo.phase = 1; + +} diff --git a/cgame/cg_screenfx.c b/cgame/cg_screenfx.c new file mode 100644 index 0000000..67be991 --- /dev/null +++ b/cgame/cg_screenfx.c @@ -0,0 +1,434 @@ +#include "cg_local.h" +#include "cg_screenfx.h" + +// this is the list of currently drawing fx and their start times and end times +screenFX_t theScreenFX; + +int CG_GetScreenEffectEndTime(int event) +{ + switch (event) + { + case SCREENFX_TRANSPORTER: + return cg.time + 1000; + case SCREENFX_SP_TRANSPORTER_IN: + return cg.time + 4000; + case SCREENFX_SP_TRANSPORTER_OUT: + return cg.time + 8000; + default: + return 0; + } +} + +// maybe play a sound or something? +void CG_BeginScreenEffect(int event) +{ + switch (event) + { + case SCREENFX_TRANSPORTER: + break; + default: + break; + } +} + + +// when adding a new effect, we'll either take an empty slot in theScreenFX or +//overwrite the oldest effect +void CG_AddFullScreenEffect(int screenfx, int clientNum) +{ + int i = 0, oldestTime = cg.time, oldestEffect = 0; + + if (clientNum != cg.predictedPlayerState.clientNum) + { // only add screen effects for our client + return; + } + for (i = 0; i < MAX_SCREENFX; i++) + { + // if we already have one of these effects going, just add to the duration of + //the existing one...don't create a new instance of the same effect + if (theScreenFX.events[i] == screenfx) + { + theScreenFX.cgStartTimes[i] = cg.time; + theScreenFX.cgEndTimes[i] = CG_GetScreenEffectEndTime(screenfx); + return; + } + else if (theScreenFX.cgStartTimes[i]) + { + if (theScreenFX.cgStartTimes[i] < oldestTime) + { + oldestTime = theScreenFX.cgStartTimes[i]; + oldestEffect = i; + } + } + //Hack-didily-ack - TiM: If were already one powerup, switch to the next + else if ( screenfx == SCREENFX_SP_TRANSPORTER_OUT ) { + if ( theScreenFX.events[i] == SCREENFX_SP_TRANSPORTER_IN ) { + theScreenFX.events[i] = 0; + theScreenFX.cgStartTimes[i] = 0; + theScreenFX.cgEndTimes[i] = 0; + } + } + else if ( screenfx == SCREENFX_SP_TRANSPORTER_IN ) { + if ( theScreenFX.events[i] == SCREENFX_SP_TRANSPORTER_OUT ) { + theScreenFX.events[i] = 0; + theScreenFX.cgStartTimes[i] = 0; + theScreenFX.cgEndTimes[i] = 0; + } + } + else + { + oldestTime = theScreenFX.cgStartTimes[i]; + oldestEffect = i; + } + } + theScreenFX.events[oldestEffect] = screenfx; + theScreenFX.cgStartTimes[oldestEffect] = cg.time; + theScreenFX.cgEndTimes[oldestEffect] = CG_GetScreenEffectEndTime(screenfx); + CG_BeginScreenEffect(screenfx); +} + + + +/* +=============== +CG_DrawScreenQuad + +=============== +*/ + + +static void CG_DrawScreenQuad(float alpha, qhandle_t screenshader) +{ + refEntity_t ent; + float radius; + + // ragePro systems can't fade blends, so don't obscure the screen + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) { + return; + } + + if (cg.refdef.fov_x > 120) + { + return; // Too wide to show this. + } + else if (cg.refdef.fov_x > 80) + { + radius = 8.0 + (cg.refdef.fov_x - 80)*0.2; + } + else + { + radius = 8.0; + } + + memset( &ent, 0, sizeof( ent ) ); + ent.reType = RT_SPRITE; + ent.renderfx = RF_FIRST_PERSON; + + VectorMA( cg.refdef.vieworg, 8, cg.refdef.viewaxis[0], ent.origin ); + + ent.data.sprite.radius = radius; + ent.customShader = screenshader; + ent.shaderRGBA[0] = alpha * 255; + ent.shaderRGBA[1] = alpha * 255; + ent.shaderRGBA[2] = alpha * 255; + ent.shaderRGBA[3] = 255; + trap_R_AddRefEntityToScene( &ent ); +} + +/* +static void CG_DrawDirectionalScreenQuad(float alpha, qhandle_t screenshader) +{ + refEntity_t ent; + vec3_t screencenter; + byte topleft, topright, lowleft, lowright, top, low, left, right; + float val; + + // ragePro systems can't fade blends, so don't obscure the screen + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) { + return; + } + + // Set up all the basic info about this refentity + VectorMA( cg.refdef.vieworg, 8, cg.refdef.viewaxis[0], screencenter); + + memset( &ent, 0, sizeof( ent ) ); + ent.reType = RT_ALPHAVERTPOLY; + ent.renderfx = RF_FIRST_PERSON; + + ent.data.sprite.radius = 4; + ent.customShader = screenshader; + * (unsigned int *) ent.shaderRGBA = 0xffffffff; + * (unsigned int *) ent.data.sprite.vertRGBA[0] = 0xffffffff; + * (unsigned int *) ent.data.sprite.vertRGBA[1] = 0xffffffff; + * (unsigned int *) ent.data.sprite.vertRGBA[2] = 0xffffffff; + * (unsigned int *) ent.data.sprite.vertRGBA[3] = 0xffffffff; + + // left + val = alpha*(0.5 + 0.5*(cg.damageX - fabs(cg.damageY))); + if (val<0) + left=0; + else if (val>1.0) + left=255; + else + left=255.0*val; + + // upper left + val = alpha*(0.5*(cg.damageX + cg.damageY)); + if (val<0) + topleft=0; + else if (val>1.0) + topleft=255; + else + topleft=255.0*val; + + // top + val = alpha*(0.5 + 0.5*(-fabs(cg.damageX) + cg.damageY)); + if (val<0) + top=0; + else if (val>1.0) + top=255; + else + top=255.0*val; + + // upper right + val = alpha*(0.5*(-cg.damageX + cg.damageY)); + if (val<0) + topright=0; + else if (val>1.0) + topright=255; + else + topright=255.0*val; + + // right + val = alpha*(0.5 + 0.5*(-cg.damageX - fabs(cg.damageY))); + if (val<0) + right=0; + else if (val>1.0) + right=255; + else + right=255.0*val; + + // lower right + val = alpha*(0.5*(-cg.damageX - cg.damageY)); + if (val<0) + lowright=0; + else if (val>1.0) + lowright=255; + else + lowright=255.0*val; + + // bottom + val = alpha*(0.5 + 0.5*(-fabs(cg.damageX) - cg.damageY)); + if (val<0) + low=0; + else if (val>1.0) + low=255; + else + low=255.0*val; + + // lower left + val = alpha*(0.5*(cg.damageX - cg.damageY)); + if (val<0) + lowleft=0; + else if (val>1.0) + lowleft=255; + else + lowleft=255.0*val; + + + // Draw the upper left corner + VectorMA(screencenter, 4, cg.refdef.viewaxis[1], ent.origin); + VectorMA(ent.origin, 4, cg.refdef.viewaxis[2], ent.origin); + ent.data.sprite.vertRGBA[0][3] = topleft; + ent.data.sprite.vertRGBA[1][3] = top; + ent.data.sprite.vertRGBA[2][3] = 0; + ent.data.sprite.vertRGBA[3][3] = left; + trap_R_AddRefEntityToScene( &ent ); + + + // Draw topper right corner + VectorMA(screencenter, -4, cg.refdef.viewaxis[1], ent.origin); + VectorMA(ent.origin, 4, cg.refdef.viewaxis[2], ent.origin); + ent.data.sprite.vertRGBA[0][3] = top; + ent.data.sprite.vertRGBA[1][3] = topright; + ent.data.sprite.vertRGBA[2][3] = right; + ent.data.sprite.vertRGBA[3][3] = 0; + trap_R_AddRefEntityToScene( &ent ); + + + // Draw lower right corner + VectorMA(screencenter, -4, cg.refdef.viewaxis[1], ent.origin); + VectorMA(ent.origin, -4, cg.refdef.viewaxis[2], ent.origin); + ent.data.sprite.vertRGBA[0][3] = 0; + ent.data.sprite.vertRGBA[1][3] = right; + ent.data.sprite.vertRGBA[2][3] = lowright; + ent.data.sprite.vertRGBA[3][3] = low; + trap_R_AddRefEntityToScene( &ent ); + + + // Draw lower left corner + VectorMA(screencenter, 4, cg.refdef.viewaxis[1], ent.origin); + VectorMA(ent.origin, -4, cg.refdef.viewaxis[2], ent.origin); + ent.data.sprite.vertRGBA[0][3] = left; + ent.data.sprite.vertRGBA[1][3] = 0; + ent.data.sprite.vertRGBA[2][3] = low; + ent.data.sprite.vertRGBA[3][3] = lowleft; + trap_R_AddRefEntityToScene( &ent ); + +} +*/ + +static void CG_DrawScreenBlob(float redalpha, float greenalpha) +{ + refEntity_t ent; + float alphascale; + float bluealpha = 0; + + // ragePro systems can't fade blends, so don't obscure the screen + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) { + return; + } + + memset( &ent, 0, sizeof( ent ) ); + ent.reType = RT_SPRITE; + ent.renderfx = RF_FIRST_PERSON; + + // Available input: + // cg.damageValue: Range from 0 to 1, indicating the amount of damage. + // cg.damageX and cg_damageY: Range from -1 to 1, indicating the location of the damage. + + VectorMA( cg.refdef.vieworg, 8, cg.refdef.viewaxis[0], ent.origin ); + VectorMA( ent.origin, cg.damageX * -8, cg.refdef.viewaxis[1], ent.origin ); + VectorMA( ent.origin, cg.damageY * 8, cg.refdef.viewaxis[2], ent.origin ); + + // Here's the scoop: The closer we are to the center, the more transparent this blob is. + alphascale = (2*fabs(cg.damageX)+fabs(cg.damageY))/3.0; + redalpha *= alphascale; + greenalpha *= alphascale; + + if (redalpha > greenalpha) + { + ent.data.sprite.radius = cg.damageValue * 15 * redalpha; + } + else + { + ent.data.sprite.radius = cg.damageShieldValue * 15 * greenalpha; + } + +/* if (redalpha < 0.01) + { // Just shield damage + ent.customShader = cgs.media.shieldBlobShader; + // Set all colors to the same as green, since the shader is green + redalpha = bluealpha = greenalpha; + } + else*/ if (greenalpha < 0.01) + { // Just pain damage + ent.customShader = cgs.media.painBlobShader; + // Set all colors to the same as red, since the shader is red + greenalpha = bluealpha = redalpha; + } + else + { // Both + ent.customShader = cgs.media.painShieldBlobShader; + } + + ent.shaderRGBA[0] = 0xff * redalpha; + ent.shaderRGBA[1] = 0xff * greenalpha; + ent.shaderRGBA[2] = 0xff * bluealpha; + ent.shaderRGBA[3] = 0xff; + trap_R_AddRefEntityToScene( &ent ); +} + + + +void CG_DrawFullScreenEffect(int screenfx, int start, int end) +{ + float alpha, alpha2 = 0.0; //TiM - second alpha + int end2, start2; + + alpha = (float)(end-cg.time)/(float)(end-start); + + switch (screenfx) + { + case SCREENFX_TRANSPORTER: + CG_DrawScreenQuad(alpha, cgs.media.teleportEffectShader); + break; + case SCREENFX_SP_TRANSPORTER_IN: + end2=end - 2500; + + CG_DrawScreenQuad( alpha, cgs.media.teleportEffectShader); + //Fade in a white quad a little later + if (cg.time <= end2 ) { + alpha2 = (float)(end2-cg.time)/(float)(end2-start); + CG_DrawScreenQuad( alpha2, cgs.media.white2Shader); + } + break; + case SCREENFX_SP_TRANSPORTER_OUT: + start2=start+2500; + end2=end - 4000; + + alpha = (float)(end2-cg.time)/(float)(end2-start); + CG_DrawScreenQuad(( 1.0f - alpha), cgs.media.teleportEffectShader); + if ( cg.time >= start2 ) { + alpha2 = (float)(end2-cg.time)/(float)(end2-start2); + if ( cg.time >= end2 ) + alpha2=0.0f; + + CG_DrawScreenQuad( ( 1.0f - alpha2), cgs.media.white2Shader); + } + break; + default: + break; + } +} + + +void CG_DrawFullScreenFX( void ) +{ + int i = 0, t; + float alpha, redalpha, greenalpha; + + if ( (cg.snap->ps.clientNum != cg.predictedPlayerState.clientNum) || cg.renderingThirdPerson ) + { + return; + } + + t = cg.time - cg.damageTime; + if ( t > 0 && t < DAMAGE_TIME) + { // Draw the blobs. + alpha = 1.0 - ((float)t / (float)DAMAGE_TIME); + + redalpha = alpha*cg.damageValue*1.5; + if (redalpha > 1.0) + { + redalpha = 1.0; + } + + greenalpha = alpha*cg.damageShieldValue*1.5; + if (greenalpha > 1.0) + { + greenalpha = 1.0; + } + + CG_DrawScreenBlob(redalpha, greenalpha); + } + + for (i = 0; i < MAX_SCREENFX; i++) + { + if (theScreenFX.cgEndTimes[i]) + { + if (theScreenFX.cgEndTimes[i] <= cg.time) + { + // remove this effect + theScreenFX.events[i] = 0; + theScreenFX.cgStartTimes[i] = 0; + theScreenFX.cgEndTimes[i] = 0; + } + else + { + // still drawing this effect + CG_DrawFullScreenEffect(theScreenFX.events[i],theScreenFX.cgStartTimes[i],theScreenFX.cgEndTimes[i]); + } + } + } +} diff --git a/cgame/cg_screenfx.h b/cgame/cg_screenfx.h new file mode 100644 index 0000000..fc43a60 --- /dev/null +++ b/cgame/cg_screenfx.h @@ -0,0 +1,28 @@ +// +// full-screen effects like beaming in/out, static from being hit, etc. +// + +enum screenfx_e +{ + SCREENFX_HIT, + SCREENFX_HALFSHIELDHIT, + SCREENFX_FULLSHIELDHIT, + SCREENFX_TRANSPORTER, + SCREENFX_SP_TRANSPORTER_IN, + SCREENFX_SP_TRANSPORTER_OUT, + MAX_SCREENFX +}; + +typedef struct screenFX_s +{ + int events[MAX_SCREENFX]; + int cgStartTimes[MAX_SCREENFX]; + int cgEndTimes[MAX_SCREENFX]; +} screenFX_t; + +extern screenFX_t theScreenFX; + +void CG_AddFullScreenEffect(int screenfx, int clientNum); + +void CG_DrawFullScreenFX(); + diff --git a/cgame/cg_servercmds.c b/cgame/cg_servercmds.c new file mode 100644 index 0000000..904ae97 --- /dev/null +++ b/cgame/cg_servercmds.c @@ -0,0 +1,931 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// 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" + +#define MAX_LOCAL_ENTITIES 512 +extern localEntity_t cg_localEntities[MAX_LOCAL_ENTITIES]; + +/* +================= +CG_ParseScores + +================= +*/ +static void CG_ParseScores( void ) { + int i, powerups, eliminated; + + 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 * 11+ 4 ) ); + cg.scores[i].score = atoi( CG_Argv( i * 11+ 5 ) ); + cg.scores[i].ping = atoi( CG_Argv( i * 11+ 6 ) ); + cg.scores[i].time = atoi( CG_Argv( i * 11+ 7 ) ); + cg.scores[i].scoreFlags = atoi( CG_Argv( i * 11+ 8 ) ); + powerups = atoi( CG_Argv( i * 11+ 9 ) ); +// cg.scores[i].faveTarget = atoi( CG_Argv( i * 12+ 10) ); +// cg.scores[i].faveTargetKills = atoi( CG_Argv( i * 12+ 11) ); + cg.scores[i].worstEnemy = atoi( CG_Argv( i * 11+ 10) ); + cg.scores[i].worstEnemyKills= atoi( CG_Argv( i * 11+ 11) ); + cg.scores[i].faveWeapon = atoi( CG_Argv( i * 11+ 12) ); + cg.scores[i].killedCnt = atoi( CG_Argv( i * 11+ 13) ); + eliminated = atoi( CG_Argv( i * 11+ 14) ); + + if ( cg.scores[i].client < 0 || cg.scores[i].client >= MAX_CLIENTS ) { + cg.scores[i].client = 0; + } + cgs.clientinfo[ cg.scores[i].client ].score = cg.scores[i].score; + cgs.clientinfo[ cg.scores[i].client ].powerups = powerups; + cgs.clientinfo[ cg.scores[i].client ].eliminated = eliminated; + } + +} + +/* +================= +CG_ParseTeamInfo + +================= +*/ +static void CG_ParseTeamInfo( void ) { + int i; + int client; + + numSortedTeamPlayers = atoi( CG_Argv( 1 ) ); + + for ( i = 0 ; i < numSortedTeamPlayers ; i++ ) { + client = atoi( CG_Argv( i * 2 + 2 ) ); //6 + + sortedTeamPlayers[i] = client; + + cgs.clientinfo[ client ].location = atoi( CG_Argv( i * 2 + 3 ) ); //6 + + /*cgs.clientinfo[ client ].health = atoi( CG_Argv( i * 6 + 4 ) ); + cgs.clientinfo[ client ].armor = atoi( CG_Argv( i * 6 + 5 ) ); + cgs.clientinfo[ client ].curWeapon = atoi( CG_Argv( i * 6 + 6 ) ); + cgs.clientinfo[ client ].powerups = atoi( CG_Argv( i * 6 + 7 ) );*/ + } +} + +/* +================= +CG_ParseHealthInfo + +================= +*/ +static void CG_ParseHealthInfo( void ) { + int i; + int client; + int numHealthInfoClients = 0; + + numHealthInfoClients = atoi( CG_Argv( 1 ) ); + + for ( i = 0 ; i < numHealthInfoClients ; i++ ) { + client = atoi( CG_Argv( i * 2 + 2 ) ); + + cgs.clientinfo[ client ].health = atoi( CG_Argv( i * 2 + 3 ) ); + } +} + +/* +================ +CG_ParseServerinfo + +This is called explicitly when the gamestate is first received, +and whenever the server updates any serverinfo flagged cvars +================ +*/ +void CG_ParseServerinfo( void ) { + const char *info; + char *mapname; + + info = CG_ConfigString( CS_SERVERINFO ); + cgs.gametype = atoi( Info_ValueForKey( info, "g_gametype" ) ); + cgs.pModAssimilation = atoi( Info_ValueForKey( info, "g_pModAssimilation" ) ); + cgs.pModDisintegration = atoi( Info_ValueForKey( info, "g_pModDisintegration" ) ); + cgs.pModActionHero = atoi( Info_ValueForKey( info, "g_pModActionHero" ) ); + cgs.pModSpecialties = atoi( Info_ValueForKey( info, "g_pModSpecialties" ) ); + cgs.pModElimination = atoi( Info_ValueForKey( info, "g_pModElimination" ) ); + cgs.dmflags = atoi( Info_ValueForKey( info, "dmflags" ) ); + cgs.teamflags = atoi( Info_ValueForKey( info, "teamflags" ) ); + cgs.fraglimit = atoi( Info_ValueForKey( info, "fraglimit" ) ); + cgs.capturelimit = atoi( Info_ValueForKey( info, "capturelimit" ) ); + cgs.timelimit = atoi( Info_ValueForKey( info, "timelimit" ) ); + cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); + cgs.ForceClassColor = atoi( Info_ValueForKey( info, "rpg_forceClassColor" ) ); + mapname = Info_ValueForKey( info, "mapname" ); + Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname ); + + //RPG-X: TiM - new Rankset + Q_strncpyz( cgs.rankSet, Info_ValueForKey( info, "rpg_rankSet"), sizeof(cgs.rankSet) ); + //RPG-X: TiM - new Class set + Q_strncpyz( cgs.classSet, Info_ValueForKey( info, "rpg_classSet" ), sizeof( cgs.classSet ) ); + + //scannable panels + cgs.scannablePanels = atoi( Info_ValueForKey( info, "rpg_scannablePanels" ) ); +} + +/* +================== +CG_ParseWarmup +================== +*/ +static void CG_ParseWarmup( void ) { + const char *info; + int warmup; + + info = CG_ConfigString( CS_WARMUP ); + + warmup = atoi( info ); + cg.warmupCount = -1; + + if ( warmup == 0 && cg.warmup ) { + + } else if ( warmup > 0 && cg.warmup <= 0 ) { + trap_S_StartLocalSound( cgs.media.countPrepareSound, CHAN_ANNOUNCER ); + } + + cg.warmup = warmup; +} + +/* +================ +CG_SetConfigValues + +Called on load to set the initial values from configure strings +================ +*/ +void CG_SetConfigValues( void ) { + const char *s; + + cgs.scores1 = atoi( CG_ConfigString( CS_SCORES1 ) ); + cgs.scores2 = atoi( CG_ConfigString( CS_SCORES2 ) ); + cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) ); + s = CG_ConfigString( CS_FLAGSTATUS ); + cgs.redflag = s[0] - '0'; + cgs.blueflag = s[1] - '0'; + cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) ); +} + +/* +===================== +CG_ClientShakeCamera + +TiM: Parses the cam shake +config string, and inputs the useful +data. +===================== +*/ +void CG_ClientShakeCamera ( void ) { + float intensity; + int duration; + char *str; + + str = (char *)CG_ConfigString( CS_CAMERA_SHAKE ); + intensity = (float)atoi( COM_Parse( &str ) )/10.0f; + duration = atoi( COM_Parse( &str ) ); //This is an offset so if a player somehow received + //the string halfway thru the cycle (ie just connected etc) + //This way, it'll only do it as much as is left for the rest of the players + cg.shake_serverIndex = duration; //Back up the index for later + duration -= ( cg.time - cgs.levelStartTime ); //This is the actual duration, based off of length, and the time the command was received + + CG_CameraShake( intensity, duration, qtrue ); +} + +/* +================== +CG_ParseClassData +================== +*/ +/*void CG_ParseClassData( void ) +{ + char *str; + int i; + char *val; + char *lineChar; + char *lineCharEnd; + int colorBits; + int classBits; + + str = (char *)CG_ConfigString( CS_CLASS_DATA ); + + if ( !str[0] ) + return; + + memset( &cgs.classData, 0, sizeof( cgs.classData ) ); + + for ( i = 0; i < MAX_CLASSES; i++ ) { + val = Info_ValueForKey( str, va( "c%i", i ) ); + + if (!val[0]) + break; + + //First slash = consoleName, so skip that + lineChar = strstr( val, "|"); + lineChar++; + + //next line should be formal name + lineCharEnd = strstr( lineChar, "|" ); + lineCharEnd--; + + val = lineChar; + val[ strlen(lineChar) - strlen(lineCharEnd) + 1] = '\0'; + + Q_strncpyz( cgs.classData[i].formalName, val, sizeof( cgs.classData[i].formalName ) ); + + //CG_Printf( S_COLOR_RED "%s\n", cgs.classData[i].formalName ); + + //--Next is color + + lineChar = lineChar + (strlen(lineChar) - strlen(lineCharEnd))+2; + + lineCharEnd = strstr( lineChar, "|" ); + lineCharEnd--; + + val = lineChar; + val[ strlen(lineChar) - strlen(lineCharEnd)+1] = '\0'; + + colorBits=atoi( val ); + cgs.classData[i].radarColor[0] = colorBits & 255; + cgs.classData[i].radarColor[1] = (colorBits >> 8) & 255; + cgs.classData[i].radarColor[2] = (colorBits >> 16) & 255; + + //CG_Printf( S_COLOR_RED "%i\n", colorBits ); + + //cgs.classData[i].showRanks = (colorBits >> 25) & 1; + + //--Next is Rank Icon Color + lineChar = lineCharEnd+2; + + classBits = atoi( lineChar ); + + cgs.classData[i].isMedic = ( classBits >> 1 ) & 1; + cgs.classData[i].showRanks = ( classBits >> 2 ) & 1; + cgs.classData[i].iconColor = ( classBits >> 4 ) & 15; + + //CG_Printf( S_COLOR_RED "%i\n", classBits ); + } +}*/ + +/* +================ +CG_ConfigStringModified + +================ +*/ +static void CG_ConfigStringModified( void ) { + const char *str; + int num; + + num = atoi( CG_Argv( 1 ) ); + + // get the gamestate from the client system, which will have the + // new configstring already integrated + trap_GetGameState( &cgs.gameState ); + + // look up the individual string that was modified + str = CG_ConfigString( num ); + + // do something with it if necessary + if ( num == CS_MUSIC ) { + CG_StartMusic(); + } else if ( num == CS_CAMERA_SHAKE ) { //RPG-X : TiM - Camera Shake + CG_ClientShakeCamera(); + } else if ( num == CS_SERVERINFO ) { + CG_ParseServerinfo(); + } else if ( num == CS_WARMUP ) { + CG_ParseWarmup(); + } else if ( num == CS_SCORES1 ) { + cgs.scores1 = atoi( str ); + } else if ( num == CS_SCORES2 ) { + cgs.scores2 = atoi( str ); + } else if ( num == CS_WARMUP ) { + CG_ParseWarmup(); + } else if ( num == CS_LEVEL_START_TIME ) { + cgs.levelStartTime = atoi( str ); + } else if ( num == CS_VOTE_TIME ) { + cgs.voteTime = atoi( str ); + cgs.voteModified = qtrue; + } else if ( num == CS_VOTE_YES ) { + cgs.voteYes = atoi( str ); + cgs.voteModified = qtrue; + } else if ( num == CS_VOTE_NO ) { + cgs.voteNo = atoi( str ); + cgs.voteModified = qtrue; + } else if ( num == CS_VOTE_STRING ) { + Q_strncpyz( cgs.voteString, str, sizeof( cgs.voteString ) ); + } 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_SOUNDS ) { + 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_DECOYS && num < CS_DECOYS+MAX_DECOYS ) { + CG_NewDecoyInfo( num - CS_DECOYS ); + } else if ( num == CS_FLAGSTATUS ) { + // format is rb where its red/blue, 0 is at base, 1 is taken, 2 is dropped + cgs.redflag = str[0] - '0'; + cgs.blueflag = str[1] - '0'; + } + #ifdef XTRA + else if(num == CS_SHADERSTATE) { + CG_ShaderStateChanged(); + } + #endif + +} + + +/* +======================= +CG_AddToTeamChat + +======================= +*/ +static void CG_AddToTeamChat( const char *str ) { + int len; + char *p, *ls; + int lastcolor; + int chatHeight; + + if (cg_teamChatHeight.integer < TEAMCHAT_HEIGHT) { + chatHeight = cg_teamChatHeight.integer; + } else { + chatHeight = TEAMCHAT_HEIGHT; + } + + if (chatHeight <= 0 || cg_teamChatTime.integer <= 0) { + // team chat disabled, dump into normal chat + cgs.teamChatPos = cgs.teamLastChatPos = 0; + return; + } + + len = 0; + + p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight]; + *p = 0; + + lastcolor = '7'; + + ls = NULL; + while (*str) { + if (len > TEAMCHAT_WIDTH - 1) { + if (ls) { + str -= (p - ls); + str++; + p -= (p - ls); + } + *p = 0; + + cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time; + + cgs.teamChatPos++; + p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight]; + *p = 0; + *p++ = Q_COLOR_ESCAPE; + *p++ = lastcolor; + len = 0; + ls = NULL; + } + + if ( Q_IsColorString( str ) ) { + *p++ = *str++; + lastcolor = *str; + *p++ = *str++; + continue; + } + if (*str == ' ') { + ls = p; + } + *p++ = *str++; + len++; + } + *p = 0; + + cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time; + cgs.teamChatPos++; + + if (cgs.teamChatPos - cgs.teamLastChatPos > chatHeight) + cgs.teamLastChatPos = cgs.teamChatPos - chatHeight; +} + + + +/* +=============== +CG_MapRestart + +The server has issued a map_restart, so the next snapshot +is completely new and should not be interpolated to. + +A tournement restart will clear everything, but doesn't +require a reload of all the media +=============== +*/ +static void CG_MapRestart( void ) { + if ( cg_showmiss.integer ) { + CG_Printf( "CG_MapRestart\n" ); + } + + CG_InitLocalEntities(); + CG_InitMarkPolys(); + + // make sure the "3 frags left" warnings play again + cg.fraglimitWarnings = 0; + + cg.timelimitWarnings = 0; + + cg.intermissionStarted = qfalse; + + cgs.voteTime = 0; + + CG_StartMusic(); + + // we really should clear more parts of cg here and stop sounds + + // play the "fight" sound if this is a restart without warmup + if ( cg.warmup == 0 /* && cgs.gametype == GT_TOURNAMENT */) + { + trap_S_StartLocalSound( cgs.media.countFightSound, CHAN_ANNOUNCER ); + } +} + +/*********************** +CG_EncodeIDFile + +The server detected that +we have a pure copy of +the ID, so it's sent us +the IP it received so we +can byte encrypt it into +an ID and save it to file +***********************/ + +static void CG_EncodeIDFile( void ) +{ + unsigned int playerID; + char *IP; + char strSubnet[3]; + int intSubnet[4]; + int i, j; + + IP = (char *)CG_Argv( 1 ); + //IP = "143.163.12.2"; + + //TiM - Scooter's IP List + //Double-check we're not spawning an ID off of these + if( Q_stricmp( IP, "localhost" ) //localhost + && Q_strncmp( IP, "10.", 3 ) //class A + && Q_strncmp( IP, "172.16.", 7 ) //class B + && Q_strncmp( IP, "192.168.", 8 ) //class C + && Q_strncmp( IP, "127.", 4 ) //loopback + && Q_strncmp( IP, "169.254.", 8 ) //link-local + ) + { + return; + } + + //check we don't already have an ID + if ( (unsigned)atoul( sv_securityCode.string ) != SECURITY_PID ) + return; + + i = 0; + j = 0; + while ( *IP ) + { + if( *IP != '.' ) + { + if ( i < 3 ) + strSubnet[i++] = *IP; + } + else + { + if ( j < 4 ) + intSubnet[j++] = atoi( strSubnet ); + + i=0; + memset( strSubnet, 0, 3 ); + } + + IP++; + } + + //the final cell + intSubnet[j++] = atoi( strSubnet ); + + //calculate the key + playerID = ( intSubnet[3] << 24 ) | ( intSubnet[2] << 16 ) | ( intSubnet[1] << 8 ) | intSubnet[0]; + + //CG_Printf( "%i %i %i %i - %u\n", intSubnet[0], intSubnet[1], intSubnet[2], intSubnet[3], playerID ); + + //encode the information into the id key file + { + fileHandle_t f; + //unsigned char buffer[SECURITY_SIZE]; + int fileLen; + rpgxSecurityFile_t sF; + + fileLen = trap_FS_FOpenFile( SECURITY_FILE, &f, FS_READ ); + + if ( !f || fileLen != SECURITY_SIZE ) + { + CG_Error( "ERROR: Could not validate %s file.\n", SECURITY_FILE ); + return; + } + + trap_FS_Read( &sF, SECURITY_SIZE, f ); + + trap_FS_FCloseFile( f ); + + if ( !sF.ID || sF.ID != SECURITY_ID ) + { + CG_Error( "ERROR: %s was loaded, but it wasn't valid.\n", SECURITY_FILE ); + return; + } + + //ensure the hash is valid + if ( sF.hash != atoul( sv_securityHash.string ) ) + { + CG_Error( "ERROR: %s was loaded, but the hash wasn't valid.\n", SECURITY_FILE ); + return; + } + + //okay, reopen the file for writing, and input the new ID + f = 0; + + fileLen = trap_FS_FOpenFile( SECURITY_FILE, &f, FS_WRITE ); + if ( !f ) + { + CG_Error( "ERROR: Could not validate %s file for writing.\n", SECURITY_FILE ); + return; + } + + //copy over the new key + sF.playerID = playerID; + + trap_FS_Write( &sF, SECURITY_SIZE, f ); + trap_FS_FCloseFile( f ); + } + + trap_Cvar_Set( "sv_SecurityCode", va( "%u", playerID ) ); +} + + +/* +================== +ConcatArgs +================== +*/ +char *ConcatArgs2( 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; +} + +#ifdef XTRA +/* +===================== +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; + } + } +} +#endif + + +/* +================= +CG_ServerCommand + +The string has been tokenized and can be retrieved with +Cmd_Argc() / Cmd_Argv() +================= +*/ +static void CG_ServerCommand( void ) { + const char *cmd; + + cmd = CG_Argv(0); + + if ( !cmd[0] ) { + // server claimed the command + return; + } + + //RPG-X | Phenix | 13/02/2005 + // Play a insult to the n00b when moved into n00b class + if ( !strcmp( cmd, "playN00bInsult") ) { + trap_S_StartLocalSound( cgs.media.N00bSound[(rand()%N00bSoundCount)], CHAN_LOCAL_SOUND ); + CG_CenterPrint( "Welcome to the n00b Class", SCREEN_HEIGHT * 0.25, BIGCHAR_WIDTH ); + return; + } + + // RPG-X | Phenix | 08/06/05 + if ( !strcmp( cmd, "servermsg") ) { + trap_S_StartLocalSound( cgs.media.AdminMsgSound, CHAN_LOCAL_SOUND ); + cg.adminMsgTime = cg.time + 10000; + Q_strncpyz( cg.adminMsgMsg, ConcatArgs2(1), sizeof( cg.adminMsgMsg ) ); + //CG_CenterPrint( cg.adminMsgMsg, SCREEN_HEIGHT * 0.25, BIGCHAR_WIDTH ); + return; + } + + if( !strcmp( cmd, "servercprint") ) { + trap_S_StartLocalSound( cgs.media.AdminMsgSound, CHAN_LOCAL_SOUND ); + //cg.adminMsgTime = cg.time + 10000; + Q_strncpyz( cg.adminMsgMsg, ConcatArgs2(1), sizeof( cg.adminMsgMsg ) ); + CG_CenterPrint( cg.adminMsgMsg, SCREEN_HEIGHT * 0.25, BIGCHAR_WIDTH ); + return; + } + + if ( !strcmp( cmd, "cp" ) ) { + CG_CenterPrint( CG_Argv(1), SCREEN_HEIGHT * 0.25, BIGCHAR_WIDTH ); + return; + } + + if ( !strcmp( cmd, "cs" ) ) { + CG_ConfigStringModified(); + return; + } + + if ( !strcmp( cmd, "print" ) ) { + CG_Printf( "%s", CG_Argv(1) ); + return; + } + + if ( !strcmp( cmd, "chat" ) ) { + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + CG_Printf( "%s\n", CG_Argv(1) ); + return; + } + + if ( !strcmp( cmd, "pc" ) ) { + trap_Cvar_Set("ui_playerClass", CG_Argv(1)); + return; + } + + if ( !strcmp( cmd, "prank" ) ) { + trap_Cvar_Set("ui_playerRank", CG_Argv(1)); + return; + } + + /*if ( !strcmp( cmd, "cr" ) ) { + trap_Cvar_VariableStringBuffer( "ui_playerclass", pClass, sizeof(pClass) ); + trap_Cvar_VariableStringBuffer( "ui_playerrank", pRank, sizeof(pRank) ); + + if ( !strcmp( pClass, "maker" ) || !strcmp( pClass, "alphaomega22" ) ) { + trap_SendClientCommand( "class command" ); + trap_SendClientCommand( va( "rank %s", pRank) ); + } + + trap_SendClientCommand( va( "class %s", pClass) ); + trap_SendClientCommand( va( "rank %s", pRank) ); + return; + }*/ + + if ( !strcmp( cmd, "tchat" ) ) { + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + CG_AddToTeamChat( CG_Argv(1) ); + CG_Printf( "%s\n", CG_Argv(1) ); + return; + } + + if ( !strcmp( cmd, "scores" ) ) { + CG_ParseScores(); + return; + } + + if ( !strcmp( cmd, "awards" ) ) { + AW_SPPostgameMenu_f(); + return; + } + + if ( !strcmp( cmd, "tinfo" ) ) { + CG_ParseTeamInfo(); + return; + } + + if ( !strcmp( cmd, "hinfo" ) ) { + CG_ParseHealthInfo(); + return; + } + + if ( !strcmp( cmd, "map_restart" ) ) { + CG_MapRestart(); + return; + } + + //TiM: Purge all active effects + if ( !strcmp( cmd, "cg_flushFX" ) ) { + int i; + + for ( i = 0; i < MAX_LOCAL_ENTITIES; i ++ ) { + cg_localEntities[i].endTime = cg.time; + } + return; + } + + /*if ( !strcmp( cmd, "cg_flushAngles" ) ) { + //CG_ResetPlayerEntity( &cg.predictedPlayerEntity ); //&cg_entities[ cg.predictedPlayerState.clientNum ] + cg_entities[cg.predictedPlayerState.clientNum].pe.torso.yawAngle = cg_entities[cg.predictedPlayerState.clientNum].lerpAngles[YAW]; + cg_entities[cg.predictedPlayerState.clientNum].pe.legs.yawAngle = cg_entities[cg.predictedPlayerState.clientNum].lerpAngles[YAW]; + return; + }*/ + + // loaddeferred can be both a servercmd and a consolecmd + if ( !strcmp( cmd, "loaddeferred" ) ) { // FIXME: spelled wrong, but not changing for demo + 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; + } + + //TiM - client received a command from a turbolift ent + //Show the decks UI + if ( !strcmp( cmd, "lift" ) ) { + trap_SendConsoleCommand( va( "ui_turbolift %i", CG_Argv( 1 ) ) ); + return; + } + + //The server motd thingzor + //RPG-X | Marcin | 15/12/2008 + if ( !strcmp( cmd, "motd" ) ) { + trap_SendConsoleCommand( "ui_motd_reset\n" ); + trap_SendConsoleCommand( "ui_motd\n" ); + return; + } + + //RPG-X | Marcin | 15/12/2008 + if ( !strcmp( cmd, "motd_line" ) ) { + trap_SendConsoleCommand( va( "ui_motd_line \"%s\"\n", CG_Argv( 1 ) ) ); + return; + } + + if ( !strcmp( cmd, "configID" ) ) + { + CG_EncodeIDFile(); + return; + } + + if ( !strcmp( cmd, "changeClientInfo" ) ) + { + //create local copy of the args + //due to the way CG_Argv works + char arg1[64]; + char arg2[64]; + + Q_strncpyz( arg1, CG_Argv(1), sizeof(arg1) ); + Q_strncpyz( arg2, CG_Argv(2), sizeof(arg2) ); + + trap_Cvar_Set( arg1, arg2 ); + return; + } + + if ( !strcmp( cmd, "playSnd" ) ) + { + trap_SendConsoleCommand( va( "play %s", CG_Argv(1) ) ); + return; + } + + if ( !strcmp( cmd, "cg_connect" ) ) { + trap_SendConsoleCommand( va( "connect %s", CG_Argv(1) ) ); + return; + } + + #ifdef XTRA + if ( Q_stricmp (cmd, "remapShader") == 0 ) + { + if (trap_Argc() == 4) + { + char shader1[MAX_QPATH]; + char shader2[MAX_QPATH]; + char shader3[MAX_QPATH]; + + Q_strncpyz(shader1, CG_Argv(1), sizeof(shader1)); + Q_strncpyz(shader2, CG_Argv(2), sizeof(shader2)); + Q_strncpyz(shader3, CG_Argv(3), sizeof(shader3)); + + trap_R_RemapShader(shader1, shader2, shader3); + } + + return; + } + #endif + + if(!strcmp(cmd, "ui_transporter")) { + trap_SendConsoleCommand(va("ui_transporter %s", CG_Argv(1))); + return; + } + + if(!strcmp(cmd, "ui_trdata")) { + trap_SendConsoleCommand(va("ui_trdata \"%s\"", CG_Argv(1))); + return; + } + + if(!strcmp(cmd, "holo_data")) { + trap_SendConsoleCommand(va("holo_data \"%s\"", CG_Argv(1))); + return; + } + + if(!strcmp(cmd, "ui_holodeck")) { + trap_SendClientCommand(va("ui_holodeck %i", CG_Argv(1))); + return; + } + + #ifdef XTRA + if(!strcmp(cmd, "sqlkey")) { + trap_SendClientCommand(va("sqlkey %i", CG_Argv(1))); + return; + } + #endif + + CG_Printf( "Unknown client game command: %s\n", cmd ); +} + + +/* +==================== +CG_ExecuteNewServerCommands + +Execute all of the server commands that were received along +with this this snapshot. +==================== +*/ +void CG_ExecuteNewServerCommands( int latestSequence ) { + while ( cgs.serverCommandSequence < latestSequence ) { + if ( trap_GetServerCommand( ++cgs.serverCommandSequence ) ) { + CG_ServerCommand(); + } + } +} diff --git a/cgame/cg_snapshot.c b/cgame/cg_snapshot.c new file mode 100644 index 0000000..c752e5d --- /dev/null +++ b/cgame/cg_snapshot.c @@ -0,0 +1,397 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_snapshot.c -- things that happen on snapshot transition, +// not necessarily every single rendered frame + +#include "cg_local.h" + + + +/* +================== +CG_ResetEntity +================== +*/ +static void CG_ResetEntity( centity_t *cent ) { + // if an event is set, assume it is new enough to use + // if the event had timed out, it would have been cleared + cent->previousEvent = 0; + + cent->trailTime = cg.snap->serverTime; + cent->thinkFlag = 0; + + VectorCopy (cent->currentState.origin, cent->lerpOrigin); + VectorCopy (cent->currentState.angles, cent->lerpAngles); + if ( cent->currentState.eType == ET_PLAYER ) { + CG_ResetPlayerEntity( cent ); + } +} + +/* +=============== +CG_TransitionEntity + +cent->nextState is moved to cent->currentState and events are fired +=============== +*/ +static void CG_TransitionEntity( centity_t *cent ) +{ +/* qboolean bNoDrawFlag = (cent->currentState.eFlags & EF_CLIENT_NODRAW); + if (cent->nextState.eFlags & EF_NODRAW) + { + // kef -- remove our special client-only flag because the game now believes we should have EF_NODRAW + bNoDrawFlag = qfalse; + } +*/ + // kef -- this will automatically remove EF_CLIENT_NODRAW because the game never sets it + cent->currentState = cent->nextState; + +/* if (bNoDrawFlag) + { + cent->currentState.eFlags |= EF_NODRAW; + cent->currentState.eFlags |= EF_CLIENT_NODRAW; + } +*/ + cent->currentValid = qtrue; + + // reset if the entity wasn't in the last frame or was teleported + if ( !cent->interpolate ) { + CG_ResetEntity( cent ); + } + + // clear the next state. if will be set by the next CG_SetNextSnap + cent->interpolate = qfalse; + + // check for events + CG_CheckEvents( cent ); +} + + +/* +================== +CG_SetInitialSnapshot + +This will only happen on the very first snapshot, or +on tourney restarts. All other times will use +CG_TransitionSnapshot instead. + +FIXME: Also called by map_restart? +================== +*/ +void CG_SetInitialSnapshot( snapshot_t *snap ) { + int i; + centity_t *cent; + entityState_t *state; + + cg.snap = snap; + + BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].currentState, qfalse ); + + // sort out solid entities + CG_BuildSolidList(); + + CG_ExecuteNewServerCommands( snap->serverCommandSequence ); + + // set our local weapon selection pointer to + // what the server has indicated the current weapon is + CG_Respawn(); + + for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { + state = &cg.snap->entities[ i ]; + cent = &cg_entities[ state->number ]; + + cent->currentState = *state; + cent->interpolate = qfalse; + cent->currentValid = qtrue; + + CG_ResetEntity( cent ); + + // check for events + CG_CheckEvents( cent ); + } +} + + +/* +=================== +CG_TransitionSnapshot + +The transition point from snap to nextSnap has passed +=================== +*/ +static void CG_TransitionSnapshot( void ) { + centity_t *cent; + snapshot_t *oldFrame; + int i; + + if ( !cg.snap ) { + CG_Error( "CG_TransitionSnapshot: NULL cg.snap" ); + return; + } + if ( !cg.nextSnap ) { + CG_Error( "CG_TransitionSnapshot: NULL cg.nextSnap" ); + return; + } + + // 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_entities[ cg.snap->entities[ i ].number ]; + cent->currentValid = qfalse; + } + + // move nextSnap to snap and do the transitions + oldFrame = cg.snap; + cg.snap = cg.nextSnap; + + BG_PlayerStateToEntityState( &cg.snap->ps, &cg_entities[ cg.snap->ps.clientNum ].currentState, qfalse ); + cg_entities[ cg.snap->ps.clientNum ].interpolate = qfalse; + + for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { + cent = &cg_entities[ cg.snap->entities[ i ].number ]; + CG_TransitionEntity( cent ); + } + + cg.nextSnap = NULL; + + // check for playerstate transition events + if ( oldFrame ) { + playerState_t *ops, *ps; + + ops = &oldFrame->ps; + ps = &cg.snap->ps; + // teleporting checks are irrespective of prediction + if ( ( ps->eFlags ^ ops->eFlags ) & EF_TELEPORT_BIT ) { + cg.thisFrameTeleport = qtrue; // will be cleared by prediction code + + //TiM - since the cg_view.c file seems to be out of scope here, + //manually reset the 3rd view + CG_ResetThirdPersonViewDamp(); + cg.thirdPersonNoLerp = qtrue; + } + + // if we are not doing client side movement prediction for any + // reason, then the client events and view changes will be issued now + if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) + || cg_nopredict.integer || cg_synchronousClients.integer ) { + CG_TransitionPlayerState( ps, ops ); + } + + } + +} + + +/* +=================== +CG_SetNextSnap + +A new snapshot has just been read in from the client system. +=================== +*/ +static void CG_SetNextSnap( snapshot_t *snap ) { + int num; + entityState_t *es; + centity_t *cent; + + cg.nextSnap = snap; + + BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].nextState, qfalse ); + cg_entities[ cg.snap->ps.clientNum ].interpolate = qtrue; + + // check for extrapolation errors + for ( num = 0 ; num < snap->numEntities ; num++ ) { + es = &snap->entities[num]; + cent = &cg_entities[ es->number ]; + + cent->nextState = *es; + + // if this frame is a teleport, or the entity wasn't in the + // previous frame, don't interpolate + if ( !cent->currentValid || ( ( cent->currentState.eFlags ^ es->eFlags ) & EF_TELEPORT_BIT ) ) { + cent->interpolate = qfalse; + } else { + cent->interpolate = qtrue; + } + } + + // if the next frame is a teleport for the playerstate, we + // can't interpolate during demos + if ( cg.snap && ( ( snap->ps.eFlags ^ cg.snap->ps.eFlags ) & EF_TELEPORT_BIT ) ) { + cg.nextFrameTeleport = qtrue; + } else { + cg.nextFrameTeleport = qfalse; + } + + // if changing follow mode, don't interpolate + if ( cg.nextSnap->ps.clientNum != cg.snap->ps.clientNum ) { + cg.nextFrameTeleport = qtrue; + } + + // if changing server restarts, don't interpolate + if ( ( cg.nextSnap->snapFlags ^ cg.snap->snapFlags ) & SNAPFLAG_SERVERCOUNT ) { + cg.nextFrameTeleport = qtrue; + } + + // sort out solid entities + CG_BuildSolidList(); +} + + +/* +======================== +CG_ReadNextSnapshot + +This is the only place new snapshots are requested +This may increment cgs.processedSnapshotNum multiple +times if the client system fails to return a +valid snapshot. +======================== +*/ +static snapshot_t *CG_ReadNextSnapshot( void ) { + qboolean r; + snapshot_t *dest; + + if ( cg.latestSnapshotNum > cgs.processedSnapshotNum + 1000 ) { + CG_Printf( "WARNING: CG_ReadNextSnapshot: way out of range, %i > %i", + cg.latestSnapshotNum, cgs.processedSnapshotNum ); + } + + while ( cgs.processedSnapshotNum < cg.latestSnapshotNum ) { + // decide which of the two slots to load it into + if ( cg.snap == &cg.activeSnapshots[0] ) { + dest = &cg.activeSnapshots[1]; + } else { + dest = &cg.activeSnapshots[0]; + } + + // try to read the snapshot from the client system + cgs.processedSnapshotNum++; + r = trap_GetSnapshot( cgs.processedSnapshotNum, dest ); + + // if it succeeded, return + if ( r ) { + CG_AddLagometerSnapshotInfo( dest ); + return dest; + } + + // a GetSnapshot will return failure if the snapshot + // never arrived, or is so old that its entities + // have been shoved off the end of the circular + // buffer in the client system. + + // record as a dropped packet + CG_AddLagometerSnapshotInfo( NULL ); + + // If there are additional snapshots, continue trying to + // read them. + } + + // nothing left to read + return NULL; +} + + +/* +============ +CG_ProcessSnapshots + +We are trying to set up a renderable view, so determine +what the simulated time is, and try to get snapshots +both before and after that time if available. + +If we don't have a valid cg.snap after exiting this function, +then a 3D game view cannot be rendered. This should only happen +right after the initial connection. After cg.snap has been valid +once, it will never turn invalid. + +Even if cg.snap is valid, cg.nextSnap may not be, if the snapshot +hasn't arrived yet (it becomes an extrapolating situation instead +of an interpolating one) + +============ +*/ +void CG_ProcessSnapshots( void ) { + snapshot_t *snap; + int n; + + // see what the latest snapshot the client system has is + trap_GetCurrentSnapshotNumber( &n, &cg.latestSnapshotTime ); + if ( n != cg.latestSnapshotNum ) { + if ( n < cg.latestSnapshotNum ) { + // this should never happen + CG_Error( "CG_ProcessSnapshots: n < cg.latestSnapshotNum" ); + } + cg.latestSnapshotNum = n; + } + + // If we have yet to receive a snapshot, check for it. + // Once we have gotten the first snapshot, cg.snap will + // always have valid data for the rest of the game + while ( !cg.snap ) { + snap = CG_ReadNextSnapshot(); + if ( !snap ) { + // we can't continue until we get a snapshot + return; + } + + // set our weapon selection to what + // the playerstate is currently using + if ( !( snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) { + CG_SetInitialSnapshot( snap ); + } + } + + // loop until we either have a valid nextSnap with a serverTime + // greater than cg.time to interpolate towards, or we run + // out of available snapshots + do { + // if we don't have a nextframe, try and read a new one in + if ( !cg.nextSnap ) { + snap = CG_ReadNextSnapshot(); + + // if we still don't have a nextframe, we will just have to + // extrapolate + if ( !snap ) { + break; + } + + CG_SetNextSnap( snap ); + + // if time went backwards, we have a level restart + if ( cg.nextSnap->serverTime < cg.snap->serverTime ) { + CG_Error( "CG_ProcessSnapshots: Server time went backwards" ); + return; + } + } + + // if our time is < nextFrame's, we have a nice interpolating state + if ( cg.time >= cg.snap->serverTime && cg.time < cg.nextSnap->serverTime ) { + break; + } + + // we have passed the transition from nextFrame to frame + CG_TransitionSnapshot(); + } while ( 1 ); + + // assert our valid conditions upon exiting + if ( cg.snap == NULL ) { + CG_Error( "CG_ProcessSnapshots: cg.snap == NULL" ); + return; + } + if ( cg.time < cg.snap->serverTime ) { + // this can happen right after a vid_restart + cg.time = cg.snap->serverTime; + } + if ( cg.nextSnap != NULL && cg.nextSnap->serverTime <= cg.time ) { + CG_Error( "CG_ProcessSnapshots: cg.nextSnap->serverTime <= cg.time" ); + } + +} + diff --git a/cgame/cg_syscalls.asm b/cgame/cg_syscalls.asm new file mode 100644 index 0000000..79166d0 --- /dev/null +++ b/cgame/cg_syscalls.asm @@ -0,0 +1,78 @@ +code + +equ trap_Print -1 +equ trap_Error -2 +equ trap_Milliseconds -3 +equ trap_Cvar_Register -4 +equ trap_Cvar_Update -5 +equ trap_Cvar_Set -6 +equ trap_Cvar_VariableStringBuffer -7 +equ trap_Argc -8 +equ trap_Argv -9 +equ trap_Args -10 +equ trap_FS_FOpenFile -11 +equ trap_FS_Read -12 +equ trap_FS_Write -13 +equ trap_FS_FCloseFile -14 +equ trap_SendConsoleCommand -15 +equ trap_AddCommand -16 +equ trap_SendClientCommand -17 +equ trap_UpdateScreen -18 +equ trap_CM_LoadMap -19 +equ trap_CM_NumInlineModels -20 +equ trap_CM_InlineModel -21 +equ trap_CM_LoadModel -22 +equ trap_CM_TempBoxModel -23 +equ trap_CM_PointContents -24 +equ trap_CM_TransformedPointContents -25 +equ trap_CM_BoxTrace -26 +equ trap_CM_TransformedBoxTrace -27 +equ trap_CM_MarkFragments -28 +equ trap_S_StartSound -29 +equ trap_S_StartLocalSound -30 +equ trap_S_ClearLoopingSounds -31 +equ trap_S_AddLoopingSound -32 +equ trap_S_UpdateEntityPosition -33 +equ trap_S_Respatialize -34 +equ trap_S_RegisterSound -35 +equ trap_S_StartBackgroundTrack -36 +equ trap_R_LoadWorldMap -37 +equ trap_R_RegisterModel -38 +equ trap_R_RegisterSkin -39 +equ trap_R_RegisterShader -40 +equ trap_R_ClearScene -41 +equ trap_R_AddRefEntityToScene -42 +equ trap_R_AddPolyToScene -43 +equ trap_R_AddLightToScene -44 +equ trap_R_RenderScene -45 +equ trap_R_SetColor -46 +equ trap_R_DrawStretchPic -47 +equ trap_R_ModelBounds -48 +equ trap_R_LerpTag -49 +equ trap_GetGlconfig -50 +equ trap_GetGameState -51 +equ trap_GetCurrentSnapshotNumber -52 +equ trap_GetSnapshot -53 +equ trap_GetServerCommand -54 +equ trap_GetCurrentCmdNumber -55 +equ trap_GetUserCmd -56 +equ trap_SetUserCmdValue -57 +equ trap_R_RegisterShaderNoMip -58 +equ trap_MemoryRemaining -59 +equ trap_R_RegisterShader3D -60 +equ trap_Cvar_Set_No_Modify -61 + +equ memset -101 +equ memcpy -102 +equ strncpy -103 +equ sin -104 +equ cos -105 +equ atan2 -106 +equ sqrt -107 +equ floor -108 +equ ceil -109 +equ testPrintInt -110 +equ testPrintFloat -111 + +equ trap_R_RemapShader -201 +equ trap_R_AddPolysToScene -202 diff --git a/cgame/cg_syscalls.c b/cgame/cg_syscalls.c new file mode 100644 index 0000000..4174214 --- /dev/null +++ b/cgame/cg_syscalls.c @@ -0,0 +1,293 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// 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" + +//TiM | Hack coz VC 6 can't understand Thilo's defnitions :S +//typedef int intptr_t; + +static intptr_t (QDECL *syscall)( intptr_t arg, ... ) = (intptr_t (QDECL *)( intptr_t, ...))-1; + + +void dllEntry( intptr_t (QDECL *syscallptr)( intptr_t arg,... ) ) { + syscall = syscallptr; +} + +/*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 ) { + syscall( CG_CVAR_REGISTER, vmCvar, varName, defaultValue, flags ); +} + +void trap_Cvar_Update( vmCvar_t *vmCvar ) { + syscall( CG_CVAR_UPDATE, vmCvar ); +} + +void trap_Cvar_Set( const char *var_name, const char *value ) { + syscall( CG_CVAR_SET, var_name, value ); +} + +void trap_Cvar_Set_No_Modify( const char *var_name, const char *value ) { + syscall( CG_CVAR_SET_NO_MODIFY, var_name, value ); +} + +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) { + syscall( CG_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize ); +} + +int trap_Argc( void ) { + return syscall( CG_ARGC ); +} + +void trap_Argv( int n, char *buffer, int bufferLength ) { + syscall( CG_ARGV, n, buffer, bufferLength ); +} + +void trap_Args( char *buffer, int bufferLength ) { + syscall( CG_ARGS, buffer, bufferLength ); +} + +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) { + return syscall( CG_FS_FOPENFILE, qpath, f, mode ); +} + +void trap_FS_Read( void *buffer, int len, fileHandle_t f ) { + syscall( CG_FS_READ, buffer, len, f ); +} + +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ) { + syscall( CG_FS_WRITE, buffer, len, f ); +} + +void trap_FS_FCloseFile( fileHandle_t f ) { + syscall( CG_FS_FCLOSEFILE, f ); +} + +void trap_SendConsoleCommand( const char *text ) { + syscall( CG_SENDCONSOLECOMMAND, text ); +} + +void trap_AddCommand( const char *cmdName ) { + syscall( CG_ADDCOMMAND, cmdName ); +} + +void trap_SendClientCommand( const char *s ) { + syscall( CG_SENDCLIENTCOMMAND, s ); +} + +void trap_UpdateScreen( void ) { + syscall( CG_UPDATESCREEN ); +} + +void trap_CM_LoadMap( const char *mapname ) { + syscall( CG_CM_LOADMAP, mapname ); +} + +int trap_CM_NumInlineModels( void ) { + return syscall( CG_CM_NUMINLINEMODELS ); +} + +clipHandle_t trap_CM_InlineModel( int index ) { + return syscall( CG_CM_INLINEMODEL, index ); +} + +clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ) { + return syscall( CG_CM_TEMPBOXMODEL, mins, maxs ); +} + +int trap_CM_PointContents( const vec3_t p, clipHandle_t model ) { + return syscall( CG_CM_POINTCONTENTS, p, model ); +} + +int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ) { + return syscall( CG_CM_TRANSFORMEDPOINTCONTENTS, p, model, origin, angles ); +} + +void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ) { + syscall( CG_CM_BOXTRACE, results, start, end, mins, maxs, model, brushmask ); +} + +void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ) { + syscall( CG_CM_TRANSFORMEDBOXTRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); +} + +int trap_CM_MarkFragments( int numPoints, const vec3_t *points, + const vec3_t projection, + int maxPoints, vec3_t pointBuffer, + int maxFragments, markFragment_t *fragmentBuffer ) { + return syscall( CG_CM_MARKFRAGMENTS, numPoints, points, projection, maxPoints, pointBuffer, maxFragments, fragmentBuffer ); +} + +void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ) { + syscall( CG_S_STARTSOUND, origin, entityNum, entchannel, sfx ); +} + +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ) { + syscall( CG_S_STARTLOCALSOUND, sfx, channelNum ); +} + +void trap_S_ClearLoopingSounds( void ) { + syscall( CG_S_CLEARLOOPINGSOUNDS ); +} + +void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) { + syscall( CG_S_ADDLOOPINGSOUND, entityNum, origin, velocity, sfx ); +} + +void trap_S_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 ) { + syscall( CG_S_STARTBACKGROUNDTRACK, intro, loop ); +} + +void trap_R_LoadWorldMap( const char *mapname ) { + syscall( CG_R_LOADWORLDMAP, mapname ); +} + +qhandle_t trap_R_RegisterModel( const char *name ) { + return syscall( CG_R_REGISTERMODEL, name ); +} + +qhandle_t trap_R_RegisterSkin( const char *name ) { + return syscall( CG_R_REGISTERSKIN, name ); +} + +qhandle_t trap_R_RegisterShader( const char *name ) { + return syscall( CG_R_REGISTERSHADER, name ); +} + +qhandle_t trap_R_RegisterShader3D( const char *name ) { + return syscall( CG_R_REGISTERSHADER3D, name ); +} + +qhandle_t trap_R_RegisterShaderNoMip( const char *name ) { + return syscall( CG_R_REGISTERSHADERNOMIP, name ); +} + +void trap_R_ClearScene( void ) { + syscall( CG_R_CLEARSCENE ); +} + +void trap_R_AddRefEntityToScene( const refEntity_t *re ) { + syscall( CG_R_ADDREFENTITYTOSCENE, re ); +} + +void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ) { + syscall( CG_R_ADDPOLYTOSCENE, hShader, numVerts, verts ); +} + +void trap_R_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_RenderScene( const refdef_t *fd ) { + syscall( CG_R_RENDERSCENE, fd ); +} + +void trap_R_SetColor( const float *rgba ) { + syscall( CG_R_SETCOLOR, rgba ); +} + +void trap_R_DrawStretchPic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ) { + syscall( CG_R_DRAWSTRETCHPIC, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), hShader ); +} + +void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) { + syscall( CG_R_MODELBOUNDS, model, mins, maxs ); +} + +void trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, + float frac, const char *tagName ) { + syscall( CG_R_LERPTAG, tag, mod, startFrame, endFrame, PASSFLOAT(frac), tagName ); +} + +void trap_GetGlconfig( glconfig_t *glconfig ) { + syscall( CG_GETGLCONFIG, glconfig ); +} + +void trap_GetGameState( gameState_t *gamestate ) { + syscall( CG_GETGAMESTATE, gamestate ); +} + +void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ) { + syscall( CG_GETCURRENTSNAPSHOTNUMBER, snapshotNumber, serverTime ); +} + +qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ) { + return syscall( CG_GETSNAPSHOT, snapshotNumber, snapshot ); +} + +qboolean trap_GetServerCommand( int serverCommandNumber ) { + return syscall( CG_GETSERVERCOMMAND, serverCommandNumber ); +} + +int trap_GetCurrentCmdNumber( void ) { + return syscall( CG_GETCURRENTCMDNUMBER ); +} + +qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ) { + return syscall( CG_GETUSERCMD, cmdNumber, ucmd ); +} + +void trap_SetUserCmdValue( int stateValue, float sensitivityScale ) { + syscall( CG_SETUSERCMDVALUE, stateValue, PASSFLOAT(sensitivityScale) ); +} + +void testPrintInt( char *string, int i ) { + syscall( CG_TESTPRINTINT, string, i ); +} + +void testPrintFloat( char *string, float f ) { + syscall( CG_TESTPRINTFLOAT, string, PASSFLOAT(f) ); +} + +int trap_MemoryRemaining( void ) { + return syscall( CG_MEMORY_REMAINING ); +} + +#ifdef XTRA +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ) { + syscall( CG_R_REMAP_SHADER, oldShader, newShader, timeOffset ); +} +#endif diff --git a/cgame/cg_text.h b/cgame/cg_text.h new file mode 100644 index 0000000..a7022af --- /dev/null +++ b/cgame/cg_text.h @@ -0,0 +1,246 @@ +#ifndef __CG_TEXT_H__ +#define __CG_TEXT_H__ + +// Ingame Text enum +typedef enum +{ + IGT_NONE, + IGT_OUTOFAMMO, + IGT_LOWAMMO, + // Scoreboard + IGT_FRAGGEDBY, + IGT_ASSIMILATEDBY, + IGT_PLACEDWITH, + + IGT_SPECTATOR, + IGT_WAITINGTOPLAY, + IGT_USETEAMMENU, + IGT_FOLLOWING, + IGT_LOADING, + IGT_AWAITINGSNAPSHOT, + IGT_PURESERVER, + IGT_CHEATSAREENABLED, + IGT_TIMELIMIT, + IGT_FRAGLIMIT, + IGT_CAPTURELIMIT, + IGT_READY, + IGT_SB_SCORE, + IGT_SB_LOC, + IGT_SB_PING, + IGT_SB_TIME, + IGT_SB_NAME, + IGT_REDTEAM, + IGT_BLUETEAM, + IGT_SAY, + IGT_SAY_TEAM, + IGT_SAY_CLASS, + + IGT_POINT_LIMIT, + IGT_TIME_LIMIT, + IGT_CAPTURE_LIMIT, + + IGT_GAME_FREEFORALL, + IGT_GAME_SINGLEPLAYER, + IGT_GAME_TOURNAMENT, + IGT_GAME_TEAMHOLOMATCH, + IGT_GAME_CAPTUREFLAG, + IGT_GAME_UNKNOWN, + + IGT_YOUELIMINATED, + IGT_YOUASSIMILATED, + IGT_PLACEWITH, + + IGT_SUICIDES, + IGT_CRATERED, + IGT_WASSQUISHED, + IGT_SANK, + IGT_MELTED, + IGT_INTOLAVA, + IGT_SAWLIGHT, + IGT_WRONGPLACE, + + IGT_TRIPPEDHERGRENADE, + IGT_TRIPPEDITSGRENADE, + IGT_TRIPPEDHISGRENADE, + IGT_BLEWHERSELFUP, + IGT_BLEWITSELFUP, + IGT_BLEWHIMSELFUP, + IGT_MELTEDHERSELF, + IGT_MELTEDITSELF, + IGT_MELTEDHIMSELF, + IGT_SMALLERGUN, + IGT_DESTROYEDHERSELF, + IGT_DESTROYEDITSELF, + IGT_DESTROYEDHIMSELF, + + IGT_TIEDFOR, + + IGT_REDTEAMLEADS, + IGT_BLUETEAMLEADS, + IGT_TO , + IGT_TEAMSARETIED, + + IGT_ATE, + IGT_GRENADE, + + IGT_WITHINBLASTRADIUS, + + IGT_ALMOSTEVADED, + IGT_PHOTONBURST, + IGT_DISPATCHEDBY, + IGT_SCAVENGERWEAPON, + + IGT_NEARLYAVOIDED, + IGT_RAILEDBY, + IGT_ELECTROCUTEDBY, + IGT_DESTROYEDBY, + IGT_TETRYONPULSE, + + IGT_BLASTEDBY, + IGT_STASISWEAPON, + IGT_WASWITHIN, + IGT_TRANSPORTERBEAM, + IGT_WASELIMINATEDBY, + IGT_WASELIMINATED, + + IGT_SNAPSHOT, + + IGT_REPLICATION_MATRIX, + IGT_HOLOGRAPHIC_PROJECTORS, + IGT_SIMULATION_DATA_BASE, + IGT_SAFETY_LOCKS, + IGT_HOLODECKSIMULATION, + IGT_USEDTEAMMENU, + IGT_CONNECTIONINTERRUPTED, + IGT_VOTE, + IGT_YES, + IGT_NO, + IGT_WAITINGFORPLAYERS, + IGT_STARTSIN, + IGT_NONETEXT, + + IGT_EFFICIENCY, + IGT_SHARPSHOOTER, + IGT_UNTOUCHABLE, + IGT_LOGISTICS, + IGT_TACTICIAN, + IGT_DEMOLITIONIST, + IGT_STREAK, + IGT_ROLE, + IGT_SECTION31, + + IGT_ACE, + IGT_EXPERT, + IGT_MASTER, + IGT_CHAMPION, + + IGT_MVP, + IGT_DEFENDER, + IGT_WARRIOR, + IGT_CARRIER, + IGT_INTERCEPTOR, + IGT_BRAVERY, + + IGT_TIED_FOR, + IGT_YOUR_RANK, + + IGT_YOUR_TEAM, + IGT_WON, + IGT_LOST, + IGT_TEAMS_TIED, + + IGT_1ST, + IGT_2ND, + IGT_3RD, + + IGT_WINNER, + IGT_CAPTURES, + IGT_POINTS, + IGT_OVERALL, + + IGT_CLICK_PLAY_AGAIN, + IGT_TITLEELIMINATED, + IGT_WORSTENEMY, + IGT_FAVORITEWEAPON, + IGT_CONNECTING, + IGT_SPECTABBREV, + + IGT_VICTOR, + IGT_DEFEATED, + + IGT_DROWNING, + IGT_CORROSION, + IGT_BOILING, + IGT_COMPRESSION, + IGT_TRANSPORTERACCIDENT, + IGT_IMPACT, + IGT_SUICIDE, + IGT_LASERBURNS, + IGT_MISADVENTURE, + IGT_PHASERBURNS, + IGT_ENERGYSCARS, + IGT_SNIPED, + IGT_INFINITEMODULATION, + IGT_GUNNEDDOWN, + IGT_SCAVENGED, + IGT_PERMANENTSTASIS, + IGT_BLASTED, + IGT_MINED, + IGT_PERFORATED, + IGT_DISRUPTED, + IGT_WELDED, + IGT_DEGAUSSED, + IGT_DESTROYED, + IGT_ANNIHILATED, + IGT_VAPORIZED, + IGT_AUTOGUNNED, + IGT_KNOCKOUT, + IGT_ASSIMILATED, + IGT_ZAPPED, + IGT_UNKNOWN, + IGT_CASUALTY, + IGT_METHOD, + IGT_OBITELIMINATED, + IGT_CREDIT, + IGT_PLEASE, + + IGT_11TH, + IGT_12TH, + IGT_13TH, + IGT_NUM_ST, + IGT_NUM_ND, + IGT_NUM_RD, + IGT_NUM_TH, + + IGT_PLAYERS, + IGT_OBJECTIVES, + + IGT_GAME_ELIMINATION, + IGT_GAME_ASSIMILATION, + IGT_GAME_ACTIONHERO, + IGT_GAME_SPECIALTIES, + IGT_GAME_DISINTEGRATION, + //RPG-X: RedTechie Added new text definitions + IGT_SB_RANK, + IGT_SB_RPGCLASS, + IGT_SB_HEALTHBARLCARS, + IGT_SB_HEALTHSTATUS1, + IGT_SB_HEALTHSTATUS2, + IGT_SB_HEALTHSTATUS3, + IGT_SB_HEALTHSTATUS4, + IGT_SB_HEALTHSTATUS5, + IGT_SB_HEALTHSTATUS6, + IGT_SB_FLIGHTSTATUS, + IGT_SB_CLOAKSTATUS, + IGT_SB_EVOSUITSTATUS, + IGT_PINGEDOUT, + IGT_FORCEFIELDDEATH, + IGT_FORCEDSUICIDE, + + IGT_MAX +} ingameTextType_t; + + +extern char *ingame_text[IGT_MAX]; + +#endif //__CG_TEXT_H__ diff --git a/cgame/cg_view.c b/cgame/cg_view.c new file mode 100644 index 0000000..2702dc7 --- /dev/null +++ b/cgame/cg_view.c @@ -0,0 +1,1707 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_view.c -- setup all the parameters (position, angle, etc) +// for a 3D rendering +#include "cg_local.h" +#include "cg_screenfx.h" + +#define MAX_SHAKE_INTENSITY 16.0f + +#define FRAMES_DOOR 16 + +/* +============================================================================= + + MODEL TESTING + +The viewthing and gun positioning tools from Q2 have been integrated and +enhanced into a single model testing facility. + +Model viewing can begin with either "testmodel " or "testgun ". + +The names must be the full pathname after the basedir, like +"models/weapons/v_launch/tris.md3" or "players/male/tris.md3" + +Testmodel will create a fake entity 100 units in front of the current view +position, directly facing the viewer. It will remain immobile, so you can +move around it to view it from different angles. + +Testgun will cause the model to follow the player around and supress the real +view weapon model. The default frame 0 of most guns is completely off screen, +so you will probably have to cycle a couple frames to see it. + +"nextframe", "prevframe", "nextskin", and "prevskin" commands will change the +frame or skin of the testmodel. These are bound to F5, F6, F7, and F8 in +q3default.cfg. + +If a gun is being tested, the "gun_x", "gun_y", and "gun_z" variables will let +you adjust the positioning. + +Note that none of the model testing features update while the game is paused, so +it may be convenient to test with deathmatch set to 1 so that bringing down the +console doesn't pause the game. + +============================================================================= +*/ + +/* +================= +CG_TestModel_f + +Creates an entity in front of the current position, which +can then be moved around +================= +*/ +void CG_TestModel_f (void) { + vec3_t angles; + + memset( &cg.testModelEntity, 0, sizeof(cg.testModelEntity) ); + if ( trap_Argc() < 2 ) { + return; + } + + Q_strncpyz (cg.testModelName, CG_Argv( 1 ), MAX_QPATH ); + cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); + + if ( trap_Argc() == 3 ) { + cg.testModelEntity.backlerp = atof( CG_Argv( 2 ) ); + cg.testModelEntity.frame = 1; + cg.testModelEntity.oldframe = 0; + } + if (! cg.testModelEntity.hModel ) { + CG_Printf( "Can't register model\n" ); + return; + } + + VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[0], cg.testModelEntity.origin ); + + angles[PITCH] = 0; + angles[YAW] = 180 + cg.refdefViewAngles[1]; + angles[ROLL] = 0; + + AnglesToAxis( angles, cg.testModelEntity.axis ); + cg.testGun = qfalse; +} + +/* +================= +CG_TestGun_f + +Replaces the current view weapon with the given model +================= +*/ +void CG_TestGun_f (void) { + CG_TestModel_f(); + cg.testGun = qtrue; + cg.testModelEntity.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON; +} + + +void CG_TestModelNextFrame_f (void) { + cg.testModelEntity.frame++; + CG_Printf( "frame %i\n", cg.testModelEntity.frame ); +} + +void CG_TestModelPrevFrame_f (void) { + cg.testModelEntity.frame--; + if ( cg.testModelEntity.frame < 0 ) { + cg.testModelEntity.frame = 0; + } + CG_Printf( "frame %i\n", cg.testModelEntity.frame ); +} + +void CG_TestModelNextSkin_f (void) { + cg.testModelEntity.skinNum++; + CG_Printf( "skin %i\n", cg.testModelEntity.skinNum ); +} + +void CG_TestModelPrevSkin_f (void) { + cg.testModelEntity.skinNum--; + if ( cg.testModelEntity.skinNum < 0 ) { + cg.testModelEntity.skinNum = 0; + } + CG_Printf( "skin %i\n", cg.testModelEntity.skinNum ); +} + +static void CG_AddTestModel (void) { + int i; + + // re-register the model, because the level may have changed + cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); + if (! cg.testModelEntity.hModel ) { + CG_Printf ("Can't register model\n"); + return; + } + + // if testing a gun, set the origin reletive to the view origin + if ( cg.testGun ) { + VectorCopy( cg.refdef.vieworg, cg.testModelEntity.origin ); + VectorCopy( cg.refdef.viewaxis[0], cg.testModelEntity.axis[0] ); + VectorCopy( cg.refdef.viewaxis[1], cg.testModelEntity.axis[1] ); + VectorCopy( cg.refdef.viewaxis[2], cg.testModelEntity.axis[2] ); + + // allow the position to be adjusted + for (i=0 ; i<3 ; i++) { + cg.testModelEntity.origin[i] += cg.refdef.viewaxis[0][i] * cg_gun_x.value; + cg.testModelEntity.origin[i] += cg.refdef.viewaxis[1][i] * cg_gun_y.value; + cg.testModelEntity.origin[i] += cg.refdef.viewaxis[2][i] * cg_gun_z.value; + } + } + + trap_R_AddRefEntityToScene( &cg.testModelEntity ); +} + +//============================================================================ + +/* +================= +CG_CalcVrect + +Sets the coordinates of the rendered window +================= +*/ +static void CG_CalcVrect (void) { + int size; + + // the intermission should allways be full screen + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { + size = 100; + } else { + // bound normal viewsize + if (cg_viewsize.integer < 30) { + trap_Cvar_Set ("cg_viewsize","30"); + size = 30; + } else if (cg_viewsize.integer > 100) { + trap_Cvar_Set ("cg_viewsize","100"); + size = 100; + } else { + size = cg_viewsize.integer; + } + + } + cg.refdef.width = cgs.glconfig.vidWidth*size * 0.01; + cg.refdef.width &= ~1; + + cg.refdef.height = cgs.glconfig.vidHeight*size * 0.01; + cg.refdef.height &= ~1; + + cg.refdef.x = (cgs.glconfig.vidWidth - cg.refdef.width) * 0.5; + cg.refdef.y = (cgs.glconfig.vidHeight - cg.refdef.height) * 0.5; +} + +//============================================================================== + +/*============================================================================== +New Third Person Camera Code +TiM: Based off of the logic of the camera code in Raven's Jedi Knight series, +however written by me, and tuned down since EF may not be that fast to handle +all of it. + +Although I consider copying someone else's logic to be somewhat lame, my programming +skills are not yet at the point I could do this by myself. I understand the concept behind +how the camera is offset and how the relevant angles are calculated, but am unsure how this +is coupled with a non-linear interpolation algorithm. +I am hoping that being able to trace how the JK code works will enlighten me to some +furthur graphical programming methodology + +Meanings of CG variables from JKA: + +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. +*/ + +//TiM: Static Global Variables +#define CAMERA_DAMP_INTERVAL 50 +#define CAMERA_SIZE 4 +#define MASK_CAMERACLIP (MASK_SOLID|CONTENTS_PLAYERCLIP) + +//Bounding Boxes for volume traces +static vec3_t cameraMins = { -CAMERA_SIZE, -CAMERA_SIZE, -CAMERA_SIZE }; +static vec3_t cameraMaxs = { CAMERA_SIZE, CAMERA_SIZE, CAMERA_SIZE }; + +//Directional Vectors +vec3_t cameraForward, cameraUp, cameraRight; + +vec3_t cameraFocusAngles, cameraFocusLoc; //location and view angles of the player's head +vec3_t cameraIdealTarget, cameraIdealLoc; //location and view angles of where the camera should be +vec3_t cameraCurTarget={0,0,0}, cameraCurLoc={0,0,0}; //Current view and location of camera +vec3_t CameraOldLoc={0,0,0}, cameraNewLoc={0,0,0}; //Backup data for the lerp func + +int cameraLastFrame=0; + +float cameraLastYaw=0; +float cameraStiffFactor=0.0f; + +qboolean freeRotate; + +/* +=============== +CG_CalcIdealThirdPersonViewTarget +TiM: +First Function : Calculate the point we should be looking at +as long as nothing is in the way +=============== +*/ +static void CG_CalcIdealThirdPersonViewTarget ( void ) { + //ATM, vieworg is the base of the feet + VectorCopy ( cg.refdef.vieworg, cameraFocusLoc ); + + //offset the Z value so it lines up with the eyeheight of the player + if ( freeRotate ) + VectorMA( cameraFocusLoc, (float)cg.predictedPlayerState.viewheight * cgs.clientinfo[cg.predictedPlayerState.clientNum].height, cameraUp, cameraFocusLoc ); + else + cameraFocusLoc[2] += (float)cg.predictedPlayerState.viewheight * cgs.clientinfo[cg.predictedPlayerState.clientNum].height; //cg.snap->ps.viewheight + + // emote based model offset + if ( cg.predictedPlayerState.stats[EMOTES] & EMOTE_LOWER ) + { + vec3_t yawForward, forward; + VectorSet( yawForward, 0, cg.predictedPlayerEntity.pe.legs.yawAngle, 0 ); + AngleVectors( yawForward, forward, NULL, NULL ); + + VectorMA( cameraFocusLoc, cgs.clientinfo[cg.predictedPlayerState.clientNum].modelOffset, forward, cameraFocusLoc ); + } + + //Transfer FocusLoc to CamTarget and use that from there + VectorCopy( cameraFocusLoc, cameraIdealTarget ); + + //Add in Horz offset + if ( cg.zoomedLeft ) { + float ratio = cg_thirdPersonRange.value * 0.02f; + if ( ratio > 1.0f ) ratio = 1.0f; + + cg_thirdPersonHorzOffset.value += ( cg_thirdPersonZoomRate.value * ratio * 0.075 ); + } + if ( cg.zoomedRight ) { + float ratio = cg_thirdPersonRange.value * 0.02f; + if ( ratio > 1.0f ) ratio = 1.0f; + + cg_thirdPersonHorzOffset.value -= ( cg_thirdPersonZoomRate.value * ratio * 0.075 ); + } + + if ( cg_thirdPersonHorzOffset.value ) { + VectorMA( cameraIdealTarget, -cg_thirdPersonHorzOffset.value, cameraRight, cameraIdealTarget ); + } + + //Add in the vertOffset + if ( cg.zoomedUp ) { + float ratio = cg_thirdPersonRange.value * 0.02f; + if ( ratio > 1.0f ) ratio = 1.0f; + + cg_thirdPersonVertOffset.value += ( cg_thirdPersonZoomRate.value * ratio * 0.075 ); + } + if ( cg.zoomedDown ) { + float ratio = cg_thirdPersonRange.value * 0.02f; + if ( ratio > 1.0f ) ratio = 1.0f; + + cg_thirdPersonVertOffset.value -= ( cg_thirdPersonZoomRate.value * ratio * 0.075 ); + } + if ( cg_thirdPersonVertOffset.value ) { + if ( freeRotate ) + VectorMA( cameraFocusLoc, cg_thirdPersonVertOffset.value, cameraUp, cameraFocusLoc ); + else + cameraIdealTarget[2] += cg_thirdPersonVertOffset.value; + } +} + +/* +=============== +CG_CalcIdealThirdPersonViewLocation +TiM: +Second Function : Calculate the point we should be looking out +from given all is good :) +=============== +*/ +static void CG_CalcIdealThirdPersonViewLocation ( void ) { + //float offset; + + if ( cg.zoomedForward ) { + cg_thirdPersonRange.value -= ( cg_thirdPersonZoomRate.value * 0.1 ); + } + else if ( cg.zoomedBackward ) { + cg_thirdPersonRange.value += ( cg_thirdPersonZoomRate.value * 0.1 ); + } + + VectorMA( cameraIdealTarget, -cg_thirdPersonRange.value, cameraForward, cameraIdealLoc ); +} + +/* +=============== +CG_ResetThirdPersonViewDamp +TiM: +Third Function : Reset all of the lerp and +set it back to normal +=============== +*/ + +void CG_ResetThirdPersonViewDamp ( void ) { + trace_t tr; + + //Clamp the pitch, so it won't cause bugs + if ( !freeRotate ) + cameraFocusAngles[PITCH] = Com_Clamp( -89.0f, 89.0f, cameraFocusAngles[PITCH] ); + + //Take our look directions and calculate vector angles + AngleVectors( cameraFocusAngles, cameraForward, cameraRight, cameraUp ); + + //Calc ideal cam target now + CG_CalcIdealThirdPersonViewTarget(); + + //Calc ideal cam view loaction now + CG_CalcIdealThirdPersonViewLocation(); + + //Take our ideal locations, and then set them to our active variables + VectorCopy( cameraIdealLoc, cameraCurLoc ); + VectorCopy( cameraIdealTarget, cameraCurTarget ); + + //Do a trace from the player's head out to the main location, in case something may be in the way + //This is mainly for stopping things like the camera going thru ceilings n stuff + CG_Trace( &tr, cameraFocusLoc, cameraMins, cameraMaxs, cameraCurTarget, cg.snap->ps.clientNum, MASK_CAMERACLIP ); + VectorCopy( tr.endpos, cameraCurTarget ); + + //Do a trace from the target to our current location to see if there's anything potentially + //blocking our view + CG_Trace( &tr, cameraCurTarget, cameraMins, cameraMaxs, cameraCurLoc, cg.snap->ps.clientNum, MASK_CAMERACLIP ); + VectorCopy( tr.endpos, cameraCurLoc ); + + //Initialise the lerp data + cameraLastFrame = cg.time; + cameraLastYaw = cameraFocusAngles[YAW]; + cameraStiffFactor = 0.0f; +} + +/* +====================== +CG_UpdateThirdPersonTargetDamp + +TiM: From the looks of this, target damp lags +the position of the camera behind a certain amount +when the player moves. The end result being a more +fluid movement. :) +Still trying to figure out how lerp actually works. +====================== +*/ +static void CG_UpdateThirdPersonTargetDamp ( void ) { + trace_t tr; + vec3_t targetDiff; //difference between our aimed target and current target + float dampFactor, dampTime, dampRatio; + + //Just to be on the safe side, let's set the current ideal data again + CG_CalcIdealThirdPersonViewTarget(); + + //if the CVAR says no delay, or if we're currently teleporting, don't do the lerp (Or we could make the player sick lol) + if ( cg_thirdPersonTargetDamp.value >= 1.0 || cg.thisFrameTeleport || cg.nextFrameTeleport || freeRotate || cg.thirdPersonNoLerp ) { + VectorCopy( cameraIdealTarget, cameraCurTarget ); + } + else if ( cg_thirdPersonTargetDamp.value >= 0.0 ) { //Okay, all's good, so let's get lerping lol + //First, let's get the difference between where we're at, and where we should be + VectorSubtract( cameraIdealTarget, cameraCurTarget, targetDiff ); + + //Ugh.... maaaaath >.< + //The JKA code says the equation is "(Damp)^(time)", so I'm guessing it's inverse exponential to + //get that cool slowy down effect :) + if ( !freeRotate ) + dampFactor = 1.0 - cg_thirdPersonTargetDamp.value; //yeh, I guess this is the inverse exponential bit. + else + dampFactor = 1.0 - Q_fabs( cameraFocusAngles[PITCH] ) / 90.0f; + + dampTime = (float)(cg.time - cameraLastFrame) * (1.0f/(float)CAMERA_DAMP_INTERVAL); //chikushou! I don't know how this time factor is caluclated O_o + + //Square this number for each unit of dampTime + dampRatio = Q_powf( dampFactor, dampTime); + + //Okay, so our current position is calulated as the difference * -ratio + ideal... O_o + VectorMA( cameraIdealTarget, -dampRatio, targetDiff, cameraCurTarget ); + } + + //Now, let's make sure we didn't lerp our way into a wall or summin + CG_Trace( &tr, cameraFocusLoc, cameraMins, cameraMaxs, cameraCurTarget, cg.snap->ps.clientNum, MASK_CAMERACLIP ); + if ( tr.fraction < 1.0 ) { + VectorCopy( tr.endpos, cameraCurTarget ); + } +} + +/* +=============== +CG_UpdateThirdPersonCameraDamp + +TiM: Okay, since the above function lagged +the camera's position, logic stands to reason +this one lags the camera's actual angles. +With the dynamic crosshair enabled, this should look pretty damn sweet. :) +Looks somewhat similar to Target Damp +================ +*/ +static void CG_UpdateThirdPersonCameraDamp ( void ) { + trace_t tr; + vec3_t locationDiff; + float dampFactor=0.0, dampTime, dampRatio; + + //Initialise our goal angle + CG_CalcIdealThirdPersonViewLocation(); + + //If we need to do any damping at all + if ( cg_thirdPersonCameraDamp.value != 0.0 ) { + float pitch; + + //get pitch, and make it all positive. Direction don't matter here + pitch = Q_fabs( cameraFocusAngles[PITCH] ); + + //If we're floating and rotate all around, perform this so the damping isn't so extreme + /*if ( pitch > 89.0f ) { + pitch = 90.0f - ( pitch - 90.0f ); + }*/ + + //The JKA code says these statments are to get it to damp less the more u look up. + //Makes sense. Still looking how tho lol + pitch /= 115.0; //magic number I guess lol. + dampFactor = (1.0-cg_thirdPersonCameraDamp.value) * ( pitch * pitch ); + + dampFactor += cg_thirdPersonCameraDamp.value; + + //the stiff factor is based off speed, so faster yaw changes seem stiffer + if ( cameraStiffFactor > 0.0f ) { + dampFactor += ( 1.0 - dampFactor) * cameraStiffFactor; + } + } + + //if our result meant no damping, or we're actively teleporting + //sigh I guess we'll need to disable dampin upon rotation. it causes absolute hell at the model's polar angles + if ( dampFactor >= 1.0 || cg.thisFrameTeleport || cg.nextFrameTeleport || freeRotate || cg.thirdPersonNoLerp ) { + VectorCopy( cameraIdealLoc, cameraCurLoc ); + } + else if ( dampFactor >= 0.0 ) { + //First, let's get the difference between where we're at, and where we should be + VectorSubtract( cameraIdealLoc, cameraCurLoc, locationDiff ); + + //Ugh.... maaaaath >.< + //The JKA code says the equation is "(Damp)^(time)", so I'm guessing it's inverse exponential to + //get that cool slowy down effect :) + dampFactor = 1.0 - dampFactor; //yeh, I guess this is the inverse exponential bit. + dampTime = (float)(cg.time - cameraLastFrame) * (1.0/(float)CAMERA_DAMP_INTERVAL); //chikushou! I don't know how this time factor is caluclated O_o + + //Square this number for each unit of dampTime + dampRatio = Q_powf( dampFactor, dampTime); + + //Okay, so our current position is calulated as the difference * -ratio + ideal... O_o + VectorMA( cameraIdealLoc, -dampRatio, locationDiff, cameraCurLoc ); + } + + //Now do a trace to see if we're all good for this loc + CG_Trace( &tr, cameraCurTarget, cameraMins, cameraMaxs, cameraCurLoc, cg.snap->ps.clientNum, MASK_CAMERACLIP ); + + //Now Raven added a huge hacky code tidbit at this stage regarding being on moving entities + //I'll see if I can get away without instituting it... + if ( tr.fraction < 1.0 ) { + VectorCopy( tr.endpos, cameraCurLoc ); + } +} + +/* +=============== +CG_OffsetThirdPersonView + +TiM: The end is nigh! +So, all of the funky code above is +finally consolidated into this main function. + +Let's see if I can understand this in any way lol +=============== +*/ +static void CG_OffsetThirdPersonView( void ) { + vec3_t diff; + float deltaYaw; + qboolean neg=qfalse; + + cameraStiffFactor = 0.0f; + + //TiM: change the math a tad if we're in free rotate mode + if ( cg.predictedPlayerEntity.currentState.eFlags & EF_FULL_ROTATE ) + freeRotate = qtrue; + else + freeRotate = qfalse; + + //copy in our raw data values + VectorCopy( cg.refdefViewAngles, cameraFocusAngles ); + + //Add a rotation offset for viewAngle + if ( cg.zoomAngleRight ) { + cg_thirdPersonAngle.value -= ( cg_thirdPersonZoomRate.value * 0.1 ); + } + if ( cg.zoomAngleLeft ) { + cg_thirdPersonAngle.value += ( cg_thirdPersonZoomRate.value * 0.1 ); + } + cameraFocusAngles[YAW] -= cg_thirdPersonAngle.value; //TiM - offset so it swings the right way lol + + //Add in pitch + if ( cg.zoomPitchUp ) { + cg_thirdPersonPitchOffset.value += ( cg_thirdPersonZoomRate.value * 0.075 ); + cg_thirdPersonPitchOffset.value = Com_Clamp( -89.0f, 89.0f, cg_thirdPersonPitchOffset.value ); + } + if ( cg.zoomPitchDown ) { + cg_thirdPersonPitchOffset.value -= ( cg_thirdPersonZoomRate.value * 0.075 ); + cg_thirdPersonPitchOffset.value = Com_Clamp( -89.0f, 89.0f, cg_thirdPersonPitchOffset.value ); + } + cameraFocusAngles[PITCH] += cg_thirdPersonPitchOffset.value; + + //if something messed up, or we're just starting, initiliaze sample + if ( cameraLastFrame == 0 || cameraLastFrame > cg.time ) { + CG_ResetThirdPersonViewDamp(); + } + else { + //Cap the final angles :) + if ( !freeRotate ) { + cameraFocusAngles[PITCH] = Com_Clamp( -80.0, 89.0, cameraFocusAngles[PITCH] ); + } + + AngleVectors( cameraFocusAngles, cameraForward, cameraRight, cameraUp ); + + deltaYaw = fabs( cameraFocusAngles[YAW] - cameraLastYaw ); + //if we exceeded our norms, stick it back + if (deltaYaw > 180.0f ) { + deltaYaw = fabs( deltaYaw - 360.0f ); + } + + cameraStiffFactor = deltaYaw / (float)(cg.time-cameraLastFrame); + if ( cameraStiffFactor < 1.0 ) { + cameraStiffFactor = 0.0; + } + else if ( cameraStiffFactor > 2.5 ) { + cameraStiffFactor = 0.75; + } + else { + cameraStiffFactor = (cameraStiffFactor-1.0f)*0.5f; + } + cameraLastYaw = cameraFocusAngles[YAW]; + + CG_UpdateThirdPersonTargetDamp(); + CG_UpdateThirdPersonCameraDamp(); + } + + VectorSubtract( cameraCurTarget, cameraCurLoc, diff ); + + //if we're hitting something, use cameraForward to calc new angles + if ( VectorNormalize(diff) == 0 || diff[0] == 0 || diff[1] == 0 ) { + VectorCopy( cameraForward, diff ); + } + + //Hack-a-dood-do. vectoangles cannot comprehend if a player is upside-down. + //It assumes it's just an opposite direction vector, so everything is rendered the right way up. >.< + //To fix this, I'll hackily copy the viewangle pitch data, and then reset the angles afterwards + + if ( freeRotate && Q_fabs( cg.refdefViewAngles[PITCH] ) > 90.0f ) + neg = qtrue; + + vectoangles( diff, cg.refdefViewAngles ); + + //Also if rotating, provide an offset when players turn fully upside down + if ( freeRotate && neg ) { + cg.refdefViewAngles[ROLL] -= 180; //AngleNormalize360( cg.refdefViewAngles[YAW] - 180); + } + + /*if ( cg_thirdPersonHorzOffset.value != 0.0f ) { + AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); + VectorMA( cameraCurLoc, cg_thirdPersonHorzOffset.value, cg.refdef.viewaxis[1], cameraCurLoc ); + }*/ + + //And update our origin lol + VectorCopy( cameraCurLoc, cg.refdef.vieworg ); + + cameraLastFrame = cg.time; +} + +#define FOCUS_DISTANCE 512 //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; + char medicrevive[32]; + int medicrevive_int; + vec3_t camPlayerPos; //TiM + + //cg.refdef.vieworg[2] += cg.predictedPlayerState.viewheight; + cg.refdef.vieworg[2] += cg.predictedPlayerState.viewheight * cgs.clientinfo[cg.predictedPlayerState.clientNum].height; + + VectorCopy( cg.refdefViewAngles, focusAngles ); + VectorCopy( cg.refdef.vieworg, camPlayerPos); //Copy the values b4 we offset; + + //RPG-X: TiM - Incorporated offsets so third person can be more dynamic + //Woo! I figured out how AngleVectors and VectorMA work!! ^_^ + AngleVectors( cg.refdefViewAngles, NULL, right, NULL); + VectorMA( cg.refdef.vieworg, cg_thirdPersonHorzOffset.value, right, cg.refdef.vieworg ); + //cg.refdef.vieworg[0] += cg_thirdPersonHorzOffset.value; + cg.refdef.vieworg[2] += cg_thirdPersonVertOffset.value; + + + // if dead, look at killer + //RPG-X: Fix camera movment when play dies with medics revive turned on + trap_Cvar_VariableStringBuffer( "rpg_medicsrevive", medicrevive, 32 ); + medicrevive_int = atoi(medicrevive); + + //TiM: Meh, you don't spin around to look at your killer in real life. O_o + //Plus, this screws up the model system :( + if(medicrevive_int == 1){ + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 1 ) { + + focusAngles[YAW] = cg_entities[cg.predictedPlayerState.clientNum].pe.legs.yawAngle; + cg.refdefViewAngles[YAW] = cg_entities[cg.predictedPlayerState.clientNum].pe.legs.yawAngle; + //focusAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; + //cg.refdefViewAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; + } + }else{ + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + + focusAngles[YAW] = cg_entities[cg.predictedPlayerState.clientNum].pe.legs.yawAngle; + cg.refdefViewAngles[YAW] = cg_entities[cg.predictedPlayerState.clientNum].pe.legs.yawAngle; + //focusAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; + //cg.refdefViewAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; + } + } + + /*if ( focusAngles[PITCH] > 89.9 ) + { + focusAngles[PITCH] = 89.9f; // don't go too far overhead - has to be under 90 or bad things happen + } + else if ( focusAngles[PITCH] < -89.9 ) //89 - Stop from going through legs + { + focusAngles[PITCH] = -89.9f; + } + + if ( cg.refdefViewAngles[PITCH] > 89.9 ) + { + cg.refdefViewAngles[PITCH] = 89.9f; // don't go too far overhead - has to be under 90 or bad things happen + } + else if ( cg.refdefViewAngles[PITCH] < -79.9 ) //89 - Stop from going through legs + { + cg.refdefViewAngles[PITCH] = -79.9f; + } + + AngleVectors( focusAngles, forward, NULL, NULL ); + + VectorMA( cg.refdef.vieworg, FOCUS_DISTANCE, forward, focusPoint ); + + VectorCopy( cg.refdef.vieworg, view ); + + view[2] += 16; + + //cg.refdefViewAngles[PITCH] *= 0.5; + + AngleVectors( cg.refdefViewAngles, forward, right, up ); + + //VectorScale( forward, cg_thirdPersonAngle.value, normalize ); + forwardScale = VectorNormalize( forward ); //cos( cg_thirdPersonAngle.value / 180 * M_PI ); + + ///VectorScale( right, cg_thirdPersonAngle.value, normalize ); + sideScale = VectorNormalize( right );//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 + + //TiM : Sometimes if the value of these variables is set to extreme numbers, they'll go thru walls. O_o + //This trace function is to fix that. + //If player is using these CVARs... + if ( cg_thirdPersonVertOffset.value != 0 || cg_thirdPersonHorzOffset.value != 0) { + //Do a trace from playermodel's head to our view location + CG_Trace( &trace, camPlayerPos, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); + + //Okay, the trace hit something... O_o + if ( trace.fraction != 1.0 ) { + //copy where it hit to our view origin. :) + VectorCopy( trace.endpos, cg.refdef.vieworg ); + } + } + + CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); + + if ( trace.fraction != 1.0 ) { + VectorCopy( trace.endpos, view ); + view[2] += (1.0 - trace.fraction) * 32; + // try another trace to this position, because a tunnel may have the ceiling + // close enogh that this is poking out + + CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); + VectorCopy( trace.endpos, view ); + } + + VectorCopy( view, cg.refdef.vieworg ); + + // select pitch to look at focus point from vieword + VectorSubtract( focusPoint, cg.refdef.vieworg, focusPoint ); + focusDist = sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] ); + if ( focusDist < 1 ) { + focusDist = 1; // should never happen + } + //cg.refdefViewAngles[PITCH] = -180 / M_PI * atan2( focusPoint[2], focusDist ); + cg.refdefViewAngles[YAW] -= cg_thirdPersonAngle.value; +}*/ + +// this causes a compiler bug on mac MrC compiler +static void CG_StepOffset( void ) { + int timeDelta; + + // smooth out stair climbing + timeDelta = cg.time - cg.stepTime; + 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; + char medicrevive[32]; + int medicrevive_int; + + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { + return; + } + + origin = cg.refdef.vieworg; + angles = cg.refdefViewAngles; + + // emote based model offset + if ( cg.predictedPlayerState.stats[EMOTES] & EMOTE_LOWER ) + { + vec3_t yawForward, forward; + VectorSet( yawForward, 0, cg.predictedPlayerEntity.pe.legs.yawAngle, 0 ); + AngleVectors( yawForward, forward, NULL, NULL ); + + VectorMA( origin, cgs.clientinfo[cg.predictedPlayerState.clientNum].modelOffset, forward, origin ); + + //CG_Printf(S_COLOR_RED "%i\n", cgs.clientinfo[cg.predictedPlayerState.clientNum].modelOffset ); + } + + // if dead, fix the angle and don't add any kick + //RPG-X: Fix camera movment when play dies with medics revive turned on + trap_Cvar_VariableStringBuffer( "rpg_medicsrevive", medicrevive, 32 ); + medicrevive_int = atoi(medicrevive); + if(medicrevive_int == 1){ + if ( cg.snap->ps.stats[STAT_HEALTH] <= 1 ) { + angles[ROLL] = 40; + angles[PITCH] = -15; + //angles[YAW] = cg.snap->ps.stats[STAT_DEAD_YAW]; + angles[YAW] = cg_entities[cg.predictedPlayerState.clientNum].pe.legs.yawAngle; + origin[2] += cg.predictedPlayerState.viewheight; + return; + } + }else{ + if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) { + angles[ROLL] = 40; + angles[PITCH] = -15; + //angles[YAW] = cg.snap->ps.stats[STAT_DEAD_YAW]; + angles[YAW] = cg_entities[cg.predictedPlayerState.clientNum].pe.legs.yawAngle; + origin[2] += cg.predictedPlayerState.viewheight; + return; + } + } + + // add angles based on weapon kick + VectorAdd (angles, cg.kick_angles, angles); + + // add angles based on damage kick + if ( cg.damageTime ) { + ratio = cg.time - cg.damageTime; + if ( ratio < DAMAGE_DEFLECT_TIME ) { + ratio /= DAMAGE_DEFLECT_TIME; + angles[PITCH] += ratio * cg.v_dmg_pitch; + angles[ROLL] += ratio * cg.v_dmg_roll; + } else { + ratio = 1.0 - ( ratio - DAMAGE_DEFLECT_TIME ) / DAMAGE_RETURN_TIME; + if ( ratio > 0 ) { + angles[PITCH] += ratio * cg.v_dmg_pitch; + angles[ROLL] += ratio * cg.v_dmg_roll; + } + } + } + + // add pitch based on fall kick +#if 0 + ratio = ( cg.time - cg.landTime) / FALL_TIME; + if (ratio < 0) + ratio = 0; + angles[PITCH] += ratio * cg.fall_value; +#endif + + // add angles based on velocity + VectorCopy( cg.predictedPlayerState.velocity, predictedVelocity ); + + delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[0]); + angles[PITCH] += delta * cg_runpitch.value; + + delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[1]); + angles[ROLL] -= delta * cg_runroll.value; + + // add angles based on bob + + // make sure the bob is visible even at low speeds + speed = cg.xyspeed > 200 ? cg.xyspeed : 200; + + delta = cg.bobfracsin * cg_bobpitch.value * speed; + if (cg.predictedPlayerState.pm_flags & PMF_DUCKED) + delta *= 3; // crouching + angles[PITCH] += delta; + delta = cg.bobfracsin * cg_bobroll.value * speed; + if (cg.predictedPlayerState.pm_flags & PMF_DUCKED) + delta *= 3; // crouching accentuates roll + if (cg.bobcycle & 1) + delta = -delta; + angles[ROLL] += delta; + +//=================================== + + // add view height + //origin[2] += cg.predictedPlayerState.viewheight; + origin[2] += (float)cg.predictedPlayerState.viewheight * cgs.clientinfo[cg.predictedPlayerState.clientNum].height; + //TiM: Model system enhancements + + // 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 + + VectorAdd (origin, cg.kick_origin, origin); + + //TiM : For rotated players + //if ( (cg.predictedPlayerEntity.currentState.eFlags & EF_FULL_ROTATE) && Q_fabs( angles[PITCH] ) > 89 ) + //angles[ROLL] += 180; + + // pivot the eye based on a neck length +//#if 0 + { +#define NECK_LENGTH 8//8 + vec3_t forward, up; + + cg.refdef.vieworg[2] -= NECK_LENGTH; + AngleVectors( cg.refdefViewAngles, forward, NULL, up ); + VectorMA( cg.refdef.vieworg, 3, forward, cg.refdef.vieworg ); + VectorMA( cg.refdef.vieworg, NECK_LENGTH, up, cg.refdef.vieworg ); + } +//#endif +} + +/* +------------------------- +CGCam_Shake +------------------------- +*/ + +void CG_CameraShake( float intensity, int duration, qboolean addRumbleSound ) +{ + if ( intensity > MAX_SHAKE_INTENSITY ) + intensity = MAX_SHAKE_INTENSITY; + + cg.shake_intensity = intensity; + cg.shake_duration = duration; + cg.shake_start = cg.time; +} + + + + +/* +------------------------- +CG_UpdateShake + +This doesn't actually affect the camera's info, but passed information instead +------------------------- +*/ + +extern void CG_ClientShakeCamera( void ); + +void CG_UpdateCameraShake( vec3_t origin, vec3_t angles ) +{ + vec3_t curOrigin, curAngle; //moveDir, + //vec3_t zero = {0, 0, 0}; + float intensity_scale, intensity; + //float ranIntensity; + float ratio; + int i; + + //TiM - restart a server loop shake + if ( cg.shake_duration <= 0 && cg.shake_serverIndex > (cg.time - cgs.levelStartTime ) ) { + CG_ClientShakeCamera(); + } + + if ( cg.shake_duration <= 0 ) { + //VectorSet( cg.shake_LastOrigin, 0, 0, 0 ); + //VectorSet( cg.shake_LastAngle, 0, 0, 0 ); + + //VectorSet( cg.shake_LerpOrigin, 0, 0, 0 ); + //VectorSet( cg.shake_LerpAngle, 0, 0, 0 ); + + memset( &cg.shake_LastOrigin, 0, sizeof( cg.shake_LastOrigin ) ); + memset( &cg.shake_LastAngle, 0, sizeof( cg.shake_LastAngle ) ); + + memset( &cg.shake_LerpOrigin, 0, sizeof( cg.shake_LerpOrigin ) ); + memset( &cg.shake_LerpAngle, 0, sizeof( cg.shake_LerpAngle ) ); + + return; + } + + //This is designed to try and make it lerp back to normal at the end + if ( cg.time > ( cg.shake_start + cg.shake_duration ) ) + { + cg.shake_intensity = 0; + cg.shake_duration = 0; + cg.shake_start = 0; + + return; + } + + //intensity_scale now also takes into account FOV with 90.0 as normal + intensity_scale = 1.0f - ( (float) ( cg.time - cg.shake_start ) / (float) cg.shake_duration ) * (cg.refdef.fov_x/90.0f); + + intensity = cg.shake_intensity * intensity_scale; + + //LerpCode + if ( cg.time > cg.shake_nextLerp ) { + + VectorCopy( cg.shake_LerpOrigin, cg.shake_LastOrigin ); + VectorCopy( cg.shake_LerpAngle, cg.shake_LastAngle ); + + //ranIntensity = flrandom( ( 10000.0f * ( 1.0f - Q_fabs( intensity ) ) ), ( 30000.0f * ( 1.0f - Q_fabs( intensity ) )) ); + + cg.shake_lastLerp = cg.shake_nextLerp; + //cg.shake_nextLerp = cg.time + (int)ranIntensity; + cg.shake_nextLerp = cg.time + irandom( 30, 40 ); + + if( cg.shake_nextLerp > ( cg.shake_start + cg.shake_duration ) ) { + cg.shake_nextLerp = (cg.shake_start + cg.shake_duration); + VectorSet( cg.shake_LerpOrigin, 0, 0, 0 ); + VectorSet( cg.shake_LerpAngle, 0, 0, 0 ); + } + else { + for (i=0; i < 3; i++ ) { + cg.shake_LerpOrigin[i] = ( crandom() * intensity ); + cg.shake_LerpAngle[i] = ( crandom() * intensity ); + } + + //If we're moving out of our boundary, away from the player... >.< + //clamp it bak in by inverting it + for ( i = 0; i < 3; i++ ) { + if ( ( cg.shake_LerpOrigin[i] + cg.shake_LastOrigin[i] ) > ( origin[i] * intensity ) ) { + cg.shake_LerpOrigin[i] = -(cg.shake_LerpOrigin[i]); + } + + if ( ( cg.shake_LerpAngle[i] + cg.shake_LastAngle[i] ) > ( angles[i] * intensity ) ) { + cg.shake_LerpAngle[i] = -(cg.shake_LerpAngle[i]); + } + } + + /*i = 0; + while ( 1 ) { + if ( ( cg.shake_LerpOrigin[i] + cg.shake_LastOrigin[i] ) > ( origin[i] * intensity ) || ( cg.shake_LerpAngle[i] + cg.shake_LastAngle[i] ) > ( angles[i] * intensity ) ) { + cg.shake_LerpOrigin[i] = crandom() * intensity; + cg.shake_LerpAngle[i] = crandom() * intensity; + } + else { + if ( i == 2 ) { + break; + } + } + i++; + if ( i == 3 ) { + i= 0; + } + }*/ + } + } + + //Com_Printf( S_COLOR_RED "NextLerp: %i, origin = { %f, %f, %f }\n", cg.shake_nextLerp, origin[0], origin[1], origin[2] ); + + //FIXME: Lerp + //TiM : Doing that + + ratio = ((float)( cg.time - cg.shake_lastLerp ) / (float)( cg.shake_nextLerp - cg.shake_lastLerp ) ); + if (ratio < 0 ) + ratio = 0.0f; + else if (ratio > 1 ) + ratio = 1.0f; + + for ( i = 0; i < 3; i++ ) { + curOrigin[i] = ratio * (float)( (origin[i] + cg.shake_LerpOrigin[i] ) - ( origin[i] + cg.shake_LastOrigin[i] ) ); //origin + } + + //Move the camera + //VectorAdd( origin, curOrigin, origin ); + VectorAdd( origin, cg.shake_LastOrigin, origin ); + VectorAdd( origin, curOrigin, origin ); + + /*for ( i=0; i < 3; i++ ) + moveDir[i] = ( crandom() * intensity );*/ + for ( i =0; i < 3; i++ ) { + curAngle[i] = ratio * (float)( ( angles[i] + cg.shake_LerpAngle[i] ) - ( angles[i] + cg.shake_LastAngle[i] ) ); //angles + } + + //FIXME: Lerp + + //Move the angles + //VectorAdd( angles, curAngle, angles ); + VectorAdd( angles, cg.shake_LastAngle, angles ); + VectorAdd( angles, curAngle, angles ); + + //Com_Printf( S_COLOR_RED "ratio: %f, origin = { %f, %f, %f }\n", ratio, origin[0], origin[1], origin[2] ); +} + + +//====================================================================== + +void CG_ZoomDown_f( void ) +{ + //if we're not holding a rifle or TR-116, don't draw + if ( !( cg.snap->ps.weapon == WP_COMPRESSION_RIFLE || cg.snap->ps.weapon == WP_TR116 ) ) { + cg.zoomed = qfalse; + cg.zoomLocked = qfalse; + return; + } + + /*if ( cg.snap->ps.persistant[PERS_CLASS] == PC_NOCLASS + || cg.snap->ps.persistant[PERS_CLASS] != PC_SECURITY + && cg.snap->ps.persistant[PERS_CLASS] != PC_ALPHAOMEGA22 + && cg.snap->ps.persistant[PERS_CLASS] != PC_ADMIN ) + {//in a class-based game, only these can zoom + cg.zoomed = qfalse; + cg.zoomLocked = qfalse; + return; + }*/ + + // The zoom hasn't been started yet, so do it now + if ( !cg.zoomed ) + { + cg.zoomLocked = qfalse; + cg.zoomed = qtrue; + cg_zoomFov.value = cg_fov.value; + cg.zoomTime = cg.time; + if ( cg.snap->ps.weapon == WP_TR116 ) { + trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.zoomStart116 ); + } + else { + trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.zoomStart ); + } + return; + } + + // Can only snap out of the zoom mode if it has already been locked (CG_ZoomUp_f has been called) + if ( cg.zoomLocked ) + { + // Snap out of zoom mode + cg.zoomed = qfalse; + cg.zoomTime = cg.time; + + if ( cg.snap->ps.weapon == WP_TR116 ) { + trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.zoomEnd116 ); + } + else { + trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.zoomEnd ); + } + } +} + +void CG_ZoomUp_f( void ) +{ + + if ( cg.zoomed ) { + // Freeze the zoom mode + cg.zoomLocked = qtrue; + } +} + +/* +==================== +CG_CalcFov + +Fixed fov at intermissions, otherwise account for fov variable and zooms. +==================== +*/ +#define WAVE_AMPLITUDE 1 +#define WAVE_FREQUENCY 0.4 + +#define FOV_MAX 120 + +static int CG_CalcFov( void ) { + float x; + float phase; + float v; + int contents; + float fov_x, fov_y; + float zoomFov; + float f; + int inwater; + qboolean warpEffect=qfalse; + + 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 = 80; + } else { + fov_x = cg_fov.value; + if ( fov_x < 1 ) { + fov_x = 1; + } else if ( fov_x > FOV_MAX ) { + fov_x = FOV_MAX; + } + } + + // account for zooms + zoomFov = cg_zoomFov.value; + if ( zoomFov < 1 ) { + zoomFov = 1; + } else if ( zoomFov > FOV_MAX) { + zoomFov = FOV_MAX; + } + + // Disable zooming when in third person + if ( cg.zoomed && !cg.renderingThirdPerson ) + { + if ( !cg.zoomLocked ) + { + // Interpolate current zoom level + cg_zoomFov.value = cg_fov.value - ((float)( cg.time - cg.zoomTime ) / ZOOM_IN_TIME + ZOOM_START_PERCENT) + * ( cg_fov.value - MAX_ZOOM_FOV ); + + // Clamp zoomFov + if ( cg_zoomFov.value < MAX_ZOOM_FOV ) + { + cg_zoomFov.value = MAX_ZOOM_FOV; + } + else if ( cg_zoomFov.value > cg_fov.value ) + { + cg_zoomFov.value = cg_fov.value; + } + else + {//still zooming + static int zoomSoundTime = 0; + + if ( zoomSoundTime < cg.time ) + { + trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_LOCAL, cgs.media.zoomLoop ); + zoomSoundTime = cg.time + 300; + } + } + } + + fov_x = cg_zoomFov.value; + } else { + f = ( cg.time - cg.zoomTime ) / (float)ZOOM_OUT_TIME; + if ( f > 1.0 ) { + fov_x = fov_x; + } else { + fov_x = zoomFov + f * ( fov_x - zoomFov ); + } + } + } + + /*if (cg.predictedPlayerState.introTime > cg.time) + { // The stuff is "holodecking in". + fov_x = 80; + }*/ + + + 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 //TiM Also do it if we're critically injured + contents = CG_PointContents( cg.refdef.vieworg, -1 ); + + warpEffect = ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) + || ( !cg.renderingThirdPerson && cg.predictedPlayerState.stats[STAT_HEALTH] <= INJURED_MODE_HEALTH && cg.predictedPlayerState.stats[STAT_HEALTH] > 1 ); + + if ( warpEffect ){ + //phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; + phase = cg.time * 0.001 * 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.zoomed ) { + cg.zoomSensitivity = 1; + } else { + cg.zoomSensitivity = cg.refdef.fov_y / 75.0; + } + + return inwater; +} + + + + +/* +=============== +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; + + // intermission view + if ( ps->pm_type == PM_INTERMISSION ) { + VectorCopy( ps->origin, cg.refdef.vieworg ); + VectorCopy( ps->viewangles, cg.refdefViewAngles ); + AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); + return CG_CalcFov(); + } + + cg.bobcycle = ( ps->bobCycle & 128 ) >> 7; + cg.bobfracsin = fabs( sin( ( ps->bobCycle & 127 ) / 127.0 * M_PI ) ); + cg.xyspeed = sqrt( ps->velocity[0] * ps->velocity[0] + + ps->velocity[1] * ps->velocity[1] ); + + + VectorCopy( ps->origin, cg.refdef.vieworg ); + VectorCopy( ps->viewangles, cg.refdefViewAngles ); + + // add error decay + if ( cg_errorDecay.value > 0 ) { + int t; + float f; + + t = cg.time - cg.predictedErrorTime; + f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; + if ( f > 0 && f < 1 ) { + VectorMA( cg.refdef.vieworg, f, cg.predictedError, cg.refdef.vieworg ); + } else { + cg.predictedErrorTime = 0; + } + } + + if ( cg.renderingThirdPerson ) { + // back away from character + CG_OffsetThirdPersonView(); + } else { + // offset for local bobbing and kicks + CG_OffsetFirstPersonView(); + } + + // shake the camera if necessary + CG_UpdateCameraShake( cg.refdef.vieworg, cg.refdefViewAngles ); + // position eye reletive to origin + AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); + + //TiM - As far as I can see, all this does is cause flashy + //effects on-screen when a player teleports the hide the delay. + //This probably doesn't really apply to us now... + /*if ( cg.hyperspace ) { + cg.refdef.rdflags |= RDF_NOWORLDMODEL | RDF_HYPERSPACE; + }*/ + + // field of view + return CG_CalcFov(); +} + + +/* +===================== +CG_PowerupTimerSounds +===================== +*/ +static void CG_PowerupTimerSounds( void ) { + int i; + int t; + + // powerup timers going away + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + t = cg.snap->ps.powerups[i]; + + // kef -- hack hack hack. additionally, hack. + if ( (PW_OUCH == i) || (PW_GHOST == i) ) + { + continue; + } + if ( t <= cg.time ) { + continue; + } + if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { + continue; + } + if ( ( t - cg.time ) / POWERUP_BLINK_TIME != ( t - cg.oldTime ) / POWERUP_BLINK_TIME ) { + //trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_ITEM, cgs.media.wearOffSound ); + } + } +} + + + +//========================================================================= + +/* +============= +CG_IntroModel + +This is when the player is starting the level. +============= +*/ +/*void CG_AddIntroModel(playerState_t *ps, int time) +{ + static int soundpoint=0, lasttime=999999; + refEntity_t doorbox; + float alpha; + byte a; + //char pClass[MAX_QPATH]; + //char pRank[MAX_QPATH]; + + if (lasttime > time) + { // Restart everything. + soundpoint=0; + } + + lasttime=time; + + // add the model + memset( &doorbox, 0, sizeof( doorbox ) ); + VectorCopy( cg.refdef.vieworg, doorbox.lightingOrigin ); + + doorbox.shaderRGBA[0] = 255; + doorbox.shaderRGBA[1] = 255; + doorbox.shaderRGBA[2] = 255; + doorbox.shaderRGBA[3] = 255; + + doorbox.hModel = cgs.media.doorbox; + if (!doorbox.hModel) { + return; + } + + VectorMA(cg.refdef.vieworg, 25, cg.refdef.viewaxis[0], doorbox.origin); + VectorMA(doorbox.origin, -35, cg.refdef.viewaxis[2], doorbox.origin); + AnglesToAxis(cg.refdefViewAngles, doorbox.axis); + + VectorScale(doorbox.axis[0], -1.0, doorbox.axis[0]); + VectorScale(doorbox.axis[1], -1.0, doorbox.axis[1]); + + if (soundpoint <= 0) + { // First part... "Prepare to compete." + if (time >= TIME_INIT) + { + soundpoint = 1; + trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.holoInitSound ); + } + doorbox.frame = 0; + } + else if (soundpoint == 1) + { // Second part... Open door after "prepare". + if (time >= TIME_DOOR_START) + { + soundpoint = 2; + trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.holoDoorSound ); + } + doorbox.frame = 0; + } + else if (soundpoint == 2) + { // Third part... Fade in after opening door. + if (time >= TIME_FADE_START) + { + soundpoint = 3; + trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.holoFadeSound ); + doorbox.frame = FRAMES_DOOR-1; + } + else + { + doorbox.frame = ((float)(time - TIME_DOOR_START) / 100.0) + 1; + if (doorbox.frame >= FRAMES_DOOR) + { + doorbox.frame=FRAMES_DOOR-1; + } + else + { + doorbox.oldframe = doorbox.frame-1; + doorbox.backlerp = (float)(doorbox.frame) - ((float)(time - TIME_DOOR_START) / 100.0); + } + } + } + else + { // Final part... Fade out the model. + + alpha = 1.0 - ((float)(time - TIME_FADE_START) / (float)TIME_FADE_DUR); + + if (alpha<0.0) + { + alpha=0.0; + } + + a=255.0*alpha; + if (a<=0) + { // An alpha of zero defaults to opaque... Makes sense, why even send something that is 100% transparent? + a=1; + } + + doorbox.shaderRGBA[0] = 255; + doorbox.shaderRGBA[1] = 255; + doorbox.shaderRGBA[2] = 255; + doorbox.shaderRGBA[3] = a; + doorbox.frame = FRAMES_DOOR-1; + } + + doorbox.renderfx |= (RF_DEPTHHACK|RF_FORCE_ENT_ALPHA|RF_FULLBRIGHT); + + trap_R_AddRefEntityToScene(&doorbox); +}*/ + +void CG_DrawEVAHelmet ( playerState_t *ps ) +{ + refEntity_t helmet; + + if ( !ps->powerups[PW_EVOSUIT] && !( cgs.clientinfo[ps->clientNum].isHazardModel && ps->powerups[PW_BOLTON] ) ) { + return; + } + + memset( &helmet, 0, sizeof(helmet) ); + VectorCopy( ps->origin, helmet.lightingOrigin ); + helmet.renderfx = RF_LIGHTING_ORIGIN | RF_DEPTHHACK | RF_FIRST_PERSON; + helmet.hModel = cgs.media.evaInterior; + + if ( !helmet.hModel ) { + CG_Printf("EVA Helmet Model not found\n"); + return; + } + + VectorCopy( cg.refdef.vieworg, helmet.origin ); + AxisCopy( cg.refdef.viewaxis, helmet.axis ); + VectorMA( helmet.origin, 6, cg.refdef.viewaxis[0], helmet.origin ); + + CG_AddRefEntityWithPowerups( &helmet, + cg.predictedPlayerEntity.currentState.powerups, + cg.predictedPlayerEntity.currentState.eFlags, + &cg.predictedPlayerEntity.beamData, + cg.predictedPlayerEntity.cloakTime, + cg.predictedPlayerEntity.decloakTime, + qfalse ); + +} + + +//========================================================================= + +/* +================= +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; + char cvarYaw[16]; //an uber long floating point value lol + float yaw; + + cg.time = serverTime; + cg.demoPlayback = demoPlayback; + + //RPG-X: TiM - Set up for giant uber rant. + //GARRRRRGGGGGGGGGGGGGGGGGGGGGGGGGHHHHHHHHHHHHHH!!!!!!!!!!!!!!!!!!!!!!!!! ->O_O<- + //Here's me looking at the BFP mod, wondering how the gravynuggets they managed + //to flip the yaw value when the players rotated beyond the normal view axis. + //I developed a totally l33tzor rotational offset algorithm that came oh so close + //to working, but after noticing a little inconsistancy in their rotation ingame, I realised + //all they did was change the player's mouse yaw CVAR to invert!!!! O_O + //Ohhhhh I just lost several years of my life and the use of my wrists..... + //Probably just as well we disabled the RPG-X online webcam... that wasn't pretty. O_o + + //load our yaw value + trap_Cvar_VariableStringBuffer( "m_yaw", cvarYaw, sizeof( cvarYaw ) ); + yaw = atof ( cvarYaw ); + + if ( cg.predictedPlayerEntity.currentState.eFlags & EF_FULL_ROTATE + && Q_fabs( cg.predictedPlayerEntity.lerpAngles[PITCH] ) > 89.0f ) + { + if ( yaw > 0.0f ) + { + //yaw = -yaw; + //trap_Cvar_Set( "m_yaw", va( "%f", yaw ) ); + trap_Cvar_Set( "m_yaw", va( "-%s", cvarYaw ) ); + + //CG_Printf( S_COLOR_RED "%f\n", yaw ); + } + } + else { //ugh... I hope no one plays with their yaws inverted. >.< This MAY need to be CVAR controlled + if ( yaw < 0.0f ) + { + char *tmp = cvarYaw; + if ( tmp[0] == '-') tmp++; //erase the neg sign + + //trap_Cvar_Set( "m_yaw", va( "%f", Q_fabs( yaw ) ) ); + trap_Cvar_Set( "m_yaw", va( "%s", tmp ) ); + } + } + + // 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; + } + + // any looped sounds will be respecified as entities + // are added to the render list + trap_S_ClearLoopingSounds(); + + // 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; + } + + // let the client system know what our weapon and zoom settings are + trap_SetUserCmdValue( cg.weaponSelect, cg.zoomSensitivity ); + + // this counter will be bumped for every valid scene we generate + cg.clientFrame++; + + // update cg.predictedPlayerState + CG_PredictPlayerState(); + + // decide on third person view + cg.renderingThirdPerson = ( cg_thirdPerson.integer && !cg.zoomed && cg.predictedPlayerState.pm_type != PM_SPECTATOR ) || (cg.snap->ps.stats[STAT_HEALTH] <= 1 ) ; //TiM - So we'll always be first person in zooming //0 + + // build cg.refdef + inwater = CG_CalcViewValues(); + + // first person blend blobs, done after AnglesToAxis + if ( !cg.renderingThirdPerson ) + { + CG_DrawFullScreenFX(); + CG_DrawEVAHelmet( &cg.predictedPlayerState ); + } + + + // build the render lists + if ( !cg.hyperspace ) { + CG_AddPacketEntities(); // adter calcViewValues, so predicted player state is correct + CG_AddMarks(); + CG_AddLocalEntities(); + } + + /*if (cg.predictedPlayerState.introTime > cg.time) + { // Render the holodeck doors + CG_AddIntroModel(&cg.predictedPlayerState, TIME_INTRO - (cg.predictedPlayerState.introTime - cg.time)); + }*/ + + CG_AddViewWeapon( &cg.predictedPlayerState ); + + // 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 + trap_S_Respatialize( cg.snap->ps.clientNum, cg.refdef.vieworg, cg.refdef.viewaxis, inwater ); + + // warning sounds when powerup is wearing off + //TiM - Not really needed :P + //CG_PowerupTimerSounds(); + + // make sure the lagometerSample and frame timing isn't done twice when in stereo + if ( stereoView != STEREO_RIGHT ) { + cg.frametime = cg.time - cg.oldTime; + if ( cg.frametime < 0 ) { + cg.frametime = 0; + } + cg.oldTime = cg.time; + CG_AddLagometerFrameInfo(); + } + + // actually issue the rendering calls + CG_DrawActive( stereoView ); + + if ( cg_stats.integer ) { + CG_Printf( "cg.clientFrame:%i\n", cg.clientFrame ); + } + + //TiM - Reset the lerp code at the end of this frame. + if ( cg.thirdPersonNoLerp ) + cg.thirdPersonNoLerp = qfalse; +} + diff --git a/cgame/cg_weapons.c b/cgame/cg_weapons.c new file mode 100644 index 0000000..828e1a4 --- /dev/null +++ b/cgame/cg_weapons.c @@ -0,0 +1,2503 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_weapons.c -- events and effects dealing with weapons +#include "cg_local.h" +#include "fx_local.h" + +//RPG-X : TiM - Weapons Arrays +static int RAweapons[8] = { WP_PADD, + WP_TRICORDER, + WP_COMPRESSION_RIFLE, + WP_TR116, + WP_VOYAGER_HYPO, + WP_DERMAL_REGEN, + WP_MEDKIT, + WP_COFFEE + }; + +static char *RAweapFileName[8] = { "padd", + "tricorder", + "prifle", + "tr116", + "hypospray", + "dermal_regen", + "medkit", + "coffeecup" + }; + + +/* +================= +CG_RegisterWeapon + +The server says this item is used on this level +================= +*/ + +// kef -- sad? yep. +typedef struct wpnBarrelInfo_s +{ + weapon_t giTag; + int numBarrels; + int flashTime; +} wpnBarrelInfo_t; + +wpnBarrelInfo_t wpnBarrelData[] = +{ + {WP_NULL_HAND, 0, 0}, + + {WP_TRICORDER, 0, 0}, + {WP_PADD, 0, 0}, + {WP_COFFEE, 0, 0}, + + {WP_PHASER, 0, 0}, + {WP_COMPRESSION_RIFLE, 0, 120}, + {WP_TR116, 1, 60}, + + {WP_GRENADE_LAUNCHER, 2, 150}, + {WP_QUANTUM_BURST, 1, 200}, + {WP_DISRUPTOR, 1, 130}, + + {WP_MEDKIT, 0, 0}, + {WP_VOYAGER_HYPO, 0, 0}, + {WP_DERMAL_REGEN, 0, 0}, + + {WP_TOOLKIT, 0, 0}, + {WP_HYPERSPANNER, 0, 0}, + + // make sure this is the last entry in this array, please + {WP_NONE, 0, 0}, +}; + +//wpnBarrelInfo_t wpnBarrelData[] = +//{ +// {WP_PHASER, 0, 0}, +// {WP_COMPRESSION_RIFLE, 0, 100}, +// {WP_NULL_HAND, 0, 0}, +// {WP_COFFEE, 0, 0}, +// {WP_DISRUPTOR, 1, 80}, +// {WP_GRENADE_LAUNCHER, 2, 140}, +// {WP_TR116, 1, 120}, +// {WP_QUANTUM_BURST, 1, 200}, +// {WP_DERMAL_REGEN, 0, 0}, +// {WP_VOYAGER_HYPO, 0, 0}, +// {WP_TOOLKIT, 0, 0}, +// {WP_MEDKIT, 0, 0}, +// {WP_TRICORDER, 0, 0}, +// {WP_PADD, 0, 0}, +// {WP_NEUTRINO_PROBE, 0, 0}, +// {WP_TR116, 0, 90}, +// +// // make sure this is the last entry in this array, please +// {WP_NONE, 0}, +//}; + +void CG_RegisterWeapon( int weaponNum ) { + weaponInfo_t *weaponInfo; + gitem_t *item, *ammo; + char path[MAX_QPATH]; + vec3_t mins, maxs; + int i; + int numBarrels = 0; + wpnBarrelInfo_t *barrelInfo = NULL; + + + weaponInfo = &cg_weapons[weaponNum]; + + if ( weaponNum == 0 ) { + return; + } + + if ( weaponInfo->registered ) { + return; + } + + memset( weaponInfo, 0, sizeof( *weaponInfo ) ); + weaponInfo->registered = qtrue; + + for ( item = bg_itemlist + 1 ; item->classname ; item++ ) { + if ( item->giType == IT_WEAPON && item->giTag == weaponNum ) { + /*if ( weaponNum == WP_DISRUPTOR ) { + Com_Printf( S_COLOR_RED "Registering %s with pickup name of %s\n", bg_itemlist[10].classname, bg_itemlist[10].pickup_name ); + }*/ + weaponInfo->item = item; + break; + } + } + if ( !item->classname ) { + CG_Error( "Couldn't find weapon %i", weaponNum ); + } + CG_RegisterItemVisuals( item - bg_itemlist ); + + weaponInfo->weaponModel = trap_R_RegisterModel( item->world_model ); + + // kef -- load in-view model + weaponInfo->viewModel = trap_R_RegisterModel(item->view_model); + + // calc midpoint for rotation + trap_R_ModelBounds( weaponInfo->weaponModel, mins, maxs ); + for ( i = 0 ; i < 3 ; i++ ) { + weaponInfo->weaponMidpoint[i] = mins[i] + 0.5 * ( maxs[i] - mins[i] ); + } + + weaponInfo->weaponIcon = trap_R_RegisterShader( item->icon ); + + for ( ammo = bg_itemlist + 1 ; ammo->classname ; ammo++ ) { + if ( ammo->giType == IT_AMMO && ammo->giTag == weaponNum ) { + break; + } + } +// if ( ammo->classname && ammo->world_model ) { +// weaponInfo->ammoModel = trap_R_RegisterModel( ammo->world_model ); +// } + + strcpy( path, item->view_model ); + COM_StripExtension( path, path ); + strcat( path, "_flash.md3" ); + weaponInfo->flashModel = trap_R_RegisterModel( path ); + + for (barrelInfo = wpnBarrelData; barrelInfo->giTag != WP_NONE; barrelInfo++) + { + if (barrelInfo->giTag == weaponNum) + { + numBarrels = barrelInfo->numBarrels; + break; + } + } + for (i=0; i< numBarrels; i++) { + Q_strncpyz( path, item->view_model, MAX_QPATH ); + COM_StripExtension( path, path ); + if (i) + { + strcat( path, va("_barrel%d.md3", i+1)); + } + else + strcat( path, "_barrel.md3" ); + weaponInfo->barrelModel[i] = trap_R_RegisterModel( path ); + } + + strcpy( path, item->view_model ); + COM_StripExtension( path, path ); + strcat( path, "_hand.md3" ); + weaponInfo->handsModel = trap_R_RegisterModel( path ); + + if ( !weaponInfo->handsModel ) { + weaponInfo->handsModel = trap_R_RegisterModel( "models/weapons2/prifle/prifle_hand.md3" ); + } + + switch ( weaponNum ) { + case WP_PHASER: + MAKERGB( weaponInfo->flashDlightColor, 0, 0, 0 ); + + weaponInfo->firingSound = trap_S_RegisterSound( SOUND_DIR "phaser/phaserfiring.wav" ); + weaponInfo->altFiringSound = trap_S_RegisterSound( SOUND_DIR "phaser/altphaserfiring.wav" ); + weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "phaser/phaserstart.wav" ); + weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "phaser/altphaserstart.wav" ); + weaponInfo->stopSound = trap_S_RegisterSound(SOUND_DIR "phaser/phaserstop.wav"); + weaponInfo->altStopSound = trap_S_RegisterSound(SOUND_DIR "phaser/altphaserstop.wav"); + + cgs.media.phaserShader = trap_R_RegisterShader( "gfx/misc/phaser_stx" ); + cgs.media.phaserEmptyShader = trap_R_RegisterShader( "gfx/misc/phaserempty" ); + + cgs.media.phaserAltShader = trap_R_RegisterShader("gfx/effects/whitelaser"); // "gfx/misc/phaser_alt" ); + + cgs.media.phaserAltEmptyShader = trap_R_RegisterShader( "gfx/misc/phaser_altempty" ); + cgs.media.phaserMuzzleEmptyShader= trap_R_RegisterShader( "models/weapons2/phaser/muzzle_empty" ); + + break; + + case WP_DERMAL_REGEN: + weaponInfo->firingSound = trap_S_RegisterSound( SOUND_DIR "dermal_regen/dm_1.wav" ); + weaponInfo->altFiringSound = trap_S_RegisterSound( SOUND_DIR "dermal_regen/dm_2.wav" ); + break; + + case WP_DISRUPTOR: + //weaponInfo->missileTrailFunc = FX_StasisProjectileThink; + weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/alien_disruptor/disruptor_bolt.md3" ); + weaponInfo->missileDlight = 70; + MAKERGB( weaponInfo->missileDlightColor, 0.0, 1.0, 0.0 ); + MAKERGB( weaponInfo->flashDlightColor, 0.0, 1.0, 0.0 ); + + weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "alien_disruptor/fire.wav" ); + weaponInfo->mainHitSound = trap_S_RegisterSound(SOUND_DIR "stasis/hit_wall.wav"); + + weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "alien_disruptor/disruptorstart.wav" ); + weaponInfo->stopSound = trap_S_RegisterSound(SOUND_DIR "alien_disruptor/disruptorstop.wav"); + weaponInfo->firingSound = trap_S_RegisterSound( SOUND_DIR "alien_disruptor/disruptorfiring.wav" ); + + cgs.media.disruptorBolt = trap_R_RegisterShader( "gfx/misc/disruptor_bolt" ); + cgs.media.disruptorStreak = trap_R_RegisterShader( "gfx/misc/disruptor_streak" ); + //cgs.media.altIMOD2Shader = trap_R_RegisterShader( "gfx/misc/IMOD2alt" ); + //cgs.media.dnBoltShader = trap_R_RegisterShader( "gfx/misc/dnBolt" ); + + cgs.media.greenParticleShader = trap_R_RegisterShader( "gfx/misc/greenparticle" ); + cgs.media.greenParticleStreakShader = trap_R_RegisterShader( "gfx/misc/greenparticle_anamorphic" ); + + cgs.media.disruptorBeam = trap_R_RegisterShader( "gfx/misc/disruptor" ); + + break; + + case WP_GRENADE_LAUNCHER: + weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/launcher/projectile.md3" ); + if(rpg_ctribgrenade.integer == 1)//RPG-X: - RedTechie Possible Hack! FIX | TiM: Heh, you're a possible hack :) + { + weaponInfo->alt_missileModel = trap_R_RegisterModel( "models/weapons2/launcher/projectile2a.md3" ); + weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "glauncher/alt_fire.wav" ); + weaponInfo->altHitSound = trap_S_RegisterSound( "sound/weapons/glauncher/beepa.wav" ); + cgs.media.grenadeAltStickSound = trap_S_RegisterSound(SOUND_DIR "glauncher/alt_stick.wav"); + + } + else + { + weaponInfo->alt_missileModel = trap_R_RegisterModel( "models/weapons2/launcher/projectile2.md3" ); + weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "glauncher/alt_fire.wav" ); + weaponInfo->altHitSound = trap_S_RegisterSound( SOUND_DIR "glauncher/beep.wav" ); + cgs.media.grenadeAltStickSound = trap_S_RegisterSound(SOUND_DIR "glauncher/alt_stick.wav"); + } + + weaponInfo->missileTrailFunc = FX_GrenadeThink; + //TiM : No flash anymore + MAKERGB( weaponInfo->flashDlightColor, 0.0, 0.0, 0.0 ); + //MAKERGB( weaponInfo->flashDlightColor, 0.6, 0.6, 1 ); + + weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "glauncher/fire.wav" ); + cgs.media.grenadeBounceSound1 = trap_S_RegisterSound(SOUND_DIR "glauncher/bounce1.wav"); + cgs.media.grenadeBounceSound2 = trap_S_RegisterSound(SOUND_DIR "glauncher/bounce2.wav"); + cgs.media.grenadeExplodeSound = trap_S_RegisterSound(SOUND_DIR "glauncher/explode.wav"); + cgs.media.grenadeAltExplodeSnd = trap_S_RegisterSound(SOUND_DIR "glauncher/alt_explode.wav" ); + + cgs.media.orangeTrailShader = trap_R_RegisterShader( "gfx/misc/orangetrail" ); + cgs.media.compressionMarkShader = trap_R_RegisterShader( "gfx/damage/burnmark1" ); + cgs.media.whiteLaserShader = trap_R_RegisterShader( "gfx/effects/whitelaser" ); + cgs.media.borgEyeFlareShader = trap_R_RegisterShader( "gfx/misc/borgeyeflare" ); + break; + + case WP_COFFEE: + //MAKERGB( weaponInfo->flashDlightColor, 1, 0.6, 0.6 ); + + /*weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "scavenger/fire.wav" ); + weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "scavenger/alt_fire.wav" ); + weaponInfo->mainHitSound = trap_S_RegisterSound(SOUND_DIR "scavenger/hit_wall.wav"); + weaponInfo->altHitSound = trap_S_RegisterSound(SOUND_DIR "scavenger/alt_explode.wav"); + weaponInfo->missileTrailFunc = FX_ScavengerProjectileThink; + weaponInfo->alt_missileTrailFunc = FX_ScavengerAltFireThink; +// weaponInfo->wiTrailTime = 100; +// weaponInfo->trailRadius = 8; + cgs.media.tetrionFlareShader = trap_R_RegisterShader( "gfx/misc/tet1" ); + cgs.media.tetrionTrail2Shader = trap_R_RegisterShader( "gfx/misc/trail2" ); + cgs.media.redFlareShader = trap_R_RegisterShader( "gfx/misc/red_flare" ); + + cgs.media.scavengerAltShader = trap_R_RegisterShader( "gfx/misc/scavaltfire" ); + cgs.media.scavExplosionFastShader = trap_R_RegisterShader( "scavExplosionFast" ); + cgs.media.scavExplosionSlowShader = trap_R_RegisterShader( "scavExplosionSlow" ); + cgs.media.compressionMarkShader = trap_R_RegisterShader( "gfx/damage/burnmark1" );*/ + break; + + case WP_QUANTUM_BURST: + MAKERGB( weaponInfo->flashDlightColor, 0.6, 0.6, 1 ); //Bluish + + weaponInfo->missileTrailFunc = FX_QuantumThink; + weaponInfo->alt_missileTrailFunc = FX_QuantumAltThink; + + weaponInfo->missileDlight = 75; + weaponInfo->alt_missileDlight = 100; + MAKERGB( weaponInfo->missileDlightColor, 1.0, 1.0, 0.5); //yellowish + + weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "quantum/fire.wav" ); + weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "quantum/alt_fire.wav" ); + + weaponInfo->mainHitSound = trap_S_RegisterSound( SOUND_DIR "quantum/hit_wall.wav" );; + weaponInfo->altHitSound = trap_S_RegisterSound( SOUND_DIR "quantum/alt_hit_wall.wav" );; + + cgs.media.whiteRingShader = trap_R_RegisterShader( "gfx/misc/whitering" ); + cgs.media.orangeRingShader = trap_R_RegisterShader( "gfx/misc/orangering" ); + cgs.media.quantumExplosionShader = trap_R_RegisterShader( "quantumExplosion" ); + cgs.media.quantumFlashShader = trap_R_RegisterShader( "yellowflash" ); + //cgs.media.bigBoomShader = trap_R_RegisterShader( "gfx/misc/bigboom" ); + cgs.media.orangeTrailShader = trap_R_RegisterShader( "gfx/misc/orangetrail" ); + cgs.media.compressionMarkShader = trap_R_RegisterShader( "gfx/damage/burnmark1" ); + cgs.media.orangeTrailShader = trap_R_RegisterShader( "gfx/misc/orangetrail" ); + cgs.media.quantumRingShader = trap_R_RegisterShader( "gfx/misc/detpack3" ); + cgs.media.quantumBoom = trap_S_RegisterSound ( SOUND_DIR "explosions/explode5.wav" ); + break; + + case WP_NULL_HAND: + /*MAKERGB( weaponInfo->flashDlightColor, 0.6, 0.6, 1 ); + + weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "IMOD/fire.wav" ); + weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "IMOD/alt_fire.wav" ); + + cgs.media.IMODShader = trap_R_RegisterShader( "gfx/misc/IMOD" ); + cgs.media.IMOD2Shader = trap_R_RegisterShader( "gfx/misc/IMOD2" ); + cgs.media.altIMODShader = trap_R_RegisterShader( "gfx/misc/IMODalt" ); + cgs.media.altIMOD2Shader = trap_R_RegisterShader( "gfx/misc/IMOD2alt" ); + cgs.media.imodExplosionShader = trap_R_RegisterShader( "imodExplosion" );*/ + break; + + case WP_COMPRESSION_RIFLE: + if(!grp_berp.integer) { + MAKERGB( weaponInfo->flashDlightColor, 0.59, 0.24, 0.25 ); + MAKERGB( weaponInfo->missileDlightColor, 0.59, 0.24, 0.25 ); + } else { + MAKERGB( weaponInfo->flashDlightColor, 0.16, 0.32, 0.5 ); + MAKERGB( weaponInfo->missileDlightColor, 0.16, 0.32, 0.5 ); + } + + weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/prifle/prifle_bolt.md3" ); + weaponInfo->missileDlight = 90; + + weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "prifle/fire.wav" ); + + weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "prifle/phaserriflestart.wav" ); + weaponInfo->altStopSound = trap_S_RegisterSound(SOUND_DIR "prifle/phaserriflestop.wav"); + weaponInfo->altFiringSound = trap_S_RegisterSound( SOUND_DIR "prifle/phaserriflefiring.wav" ); + + weaponInfo->mainHitSound = trap_S_RegisterSound( SOUND_DIR "prifle/impact.wav" );; + + cgs.media.prifleImpactShader = trap_R_RegisterShader( "gfx/effects/prifle_hit" ); + cgs.media.compressionAltBeamShader = trap_R_RegisterShader( "gfx/effects/prifle_altbeam" ); + cgs.media.compressionAltBlastShader = trap_R_RegisterShader( "gfx/effects/prifle_altblast" ); + cgs.media.compressionMarkShader = trap_R_RegisterShader( "gfx/damage/burnmark1" ); + cgs.media.prifleBolt = trap_R_RegisterShader( "gfx/misc/priflebolt" ); + + cgs.media.liteRedParticleStreakShader = trap_R_RegisterShader( "gfx/misc/literedparticle_anamorphic" ); + cgs.media.liteRedParticleShader = trap_R_RegisterShader( "gfx/misc/literedparticle" ); + + cgs.media.flashlightModel = trap_R_RegisterModel( "models/weapons2/prifle/prifle_flashlight.md3" ); //RPG-X : TiM - flashlight model + + cgs.media.prifleBeam = trap_R_RegisterShader( "gfx/misc/phaser_rifle" ); + + break; + +/* case WP_TR116: + MAKERGB( weaponInfo->flashDlightColor, 0.16, 0.16, 1 ); + weaponInfo->flashSound = trap_S_RegisterSound( "sound/weapons/hitonhead.wav" ); + weaponInfo->altFlashSnd = trap_S_RegisterSound( "sound/weapons/guncharge.wav" ); + cgs.media.tetrionTrail2Shader = trap_R_RegisterShader( "gfx/misc/trail2" ); + cgs.media.compressionMarkShader = trap_R_RegisterShader( "gfx/damage/burnmark1" ); + weaponInfo->mainHitSound = trap_S_RegisterSound( SOUND_DIR "prifle/impact.wav" ); + break;*/ + /* + case WP_TR116: //OLD CODE (replaced for TR116) + MAKERGB( weaponInfo->flashDlightColor, 0.6, 0.6, 1 ); + + weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "tetrion/fire.wav" ); + weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "tetrion/alt_fire.wav" ); + cgs.media.tetrionRicochetSound1 = trap_S_RegisterSound(SOUND_DIR "tetrion/ricochet1.wav"); + cgs.media.tetrionRicochetSound2 = trap_S_RegisterSound(SOUND_DIR "tetrion/ricochet2.wav"); + cgs.media.tetrionRicochetSound3 = trap_S_RegisterSound(SOUND_DIR "tetrion/ricochet3.wav"); + + weaponInfo->missileTrailFunc = FX_TetrionProjectileThink; + weaponInfo->alt_missileTrailFunc = FX_TetrionProjectileThink; + + cgs.media.greenBurstShader = trap_R_RegisterShader( "gfx/misc/greenburst" ); + cgs.media.greenTrailShader = trap_R_RegisterShader( "gfx/misc/greentrail" ); + cgs.media.tetrionTrail2Shader = trap_R_RegisterShader( "gfx/misc/trail2" ); + cgs.media.tetrionFlareShader = trap_R_RegisterShader( "gfx/misc/tet1" ); + cgs.media.borgFlareShader = trap_R_RegisterShader( "gfx/misc/borgflare" ); + cgs.media.bulletmarksShader = trap_R_RegisterShader( "textures/decals/bulletmark4" ); + break; +*/ + case WP_VOYAGER_HYPO: + weaponInfo->flashSound = weaponInfo->altFlashSnd = trap_S_RegisterSound( "sound/items/jetpuffmed.wav" ); + break; + + case WP_TRICORDER: + weaponInfo->firingSound= trap_S_RegisterSound( "sound/items/tricorderscan.wav" ); //altFlashSnd + weaponInfo->altFiringSound = trap_S_RegisterSound( "sound/ambience/voyager/medictricorder.wav" ); //flashSound + + //weaponInfo->isAnimSndBased = qtrue; + break; + + case WP_PADD: + weaponInfo->firingSound = trap_S_RegisterSound( SOUND_DIR "padd/padd_1.wav" ); //flashSound + weaponInfo->altFiringSound = trap_S_RegisterSound( SOUND_DIR "padd/padd_2.wav" ); //altFlashSnd + + weaponInfo->isAnimSndBased = qtrue; + break; + + case WP_HYPERSPANNER: + weaponInfo->firingSound = trap_S_RegisterSound( SOUND_DIR "hyperspanner/spanner_1.wav" ); + weaponInfo->altFiringSound = trap_S_RegisterSound( SOUND_DIR "hyperspanner/spanner_2.wav" ); + break; + + case WP_TR116: + weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "hitonhead.wav" ); + weaponInfo->altFlashSnd = weaponInfo->flashSound; + //weaponInfo->altFlashSnd = trap_S_RegisterSound( "sound/weapons/guncharge.wav" ); + break; + +//Toolkit + case WP_TOOLKIT: + weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "toolkit/toolkit_1.wav" ); + weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "toolkit/toolkit_2.wav" ); + break; + +//Medkit + case WP_MEDKIT: + weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "medkit/medkit_1.wav" ); + weaponInfo->altFlashSnd = trap_S_RegisterSound( SOUND_DIR "medkit/medkit_2.wav" ); + break; + + default: + MAKERGB( weaponInfo->flashDlightColor, 1, 1, 1 ); + weaponInfo->flashSound = trap_S_RegisterSound( SOUND_DIR "prifle/fire.wav" ); + break; + } +} + +/* +================= +CG_RegisterItemVisuals + +The server says this item is used on this level +================= +*/ +void CG_RegisterItemVisuals( int itemNum ) { + itemInfo_t *itemInfo; + gitem_t *item; + + itemInfo = &cg_items[ itemNum ]; + if ( itemInfo->registered ) { + return; + } + + item = &bg_itemlist[ itemNum ]; + + memset( itemInfo, 0, sizeof( &itemInfo ) ); + itemInfo->registered = qtrue; + + itemInfo->model = trap_R_RegisterModel( item->world_model ); + + itemInfo->icon = trap_R_RegisterShader( item->icon ); + + if ( item->giType == IT_WEAPON ) { + CG_RegisterWeapon( item->giTag ); + } + + // since the seeker uses the scavenger rifes sounds, we must precache the scavenger rifle stuff if we hit the item seeker +/* if ( item->giTag == PW_FLASHLIGHT) + { + CG_RegisterWeapon( WP_COFFEE ); + }*/ + + // hang onto the handles for holdable items in case they're useable (e.g. detpack) +/* if (IT_HOLDABLE == item->giType) + { + // sanity check + if ( (item->giTag < HI_NUM_HOLDABLE) && (item->giTag > 0) ) // use "> 0" cuz first slot should be empty + { + if (item->world_model[1]) + { + cgs.useableModels[item->giTag] = trap_R_RegisterModel( item->useablemodel ); + } + else + { + cgs.useableModels[item->giTag] = itemInfo->model]; + } + } + } +*/ +} + + +/* +======================================================================================== + +VIEW WEAPON + +======================================================================================== +*/ + +/* +================= +CG_MapTorsoToWeaponFrame + +================= +*/ +static int CG_MapTorsoToWeaponFrame( clientInfo_t *ci, int frame ) { + animation_t *anim; + + // change weapon + anim = &cg_animsList[ci->animIndex].animations[TORSO_DROPWEAP1]; + if ( frame >= anim->firstFrame + && frame < anim->firstFrame + 9 ) { + return frame - anim->firstFrame + 6; + } + + // stand attack + anim = &cg_animsList[ci->animIndex].animations[BOTH_ATTACK3]; + if ( frame >= anim->firstFrame && frame < anim->firstFrame + 6 ) { + return 1 + frame - anim->firstFrame; + } + + // stand attack 2 + anim = &cg_animsList[ci->animIndex].animations[BOTH_ATTACK2]; + if ( frame >= anim->firstFrame && frame < anim->firstFrame + 6 ) { + return 1 + frame - anim->firstFrame; + } + + anim = &cg_animsList[ci->animIndex].animations[TORSO_WEAPONREADY1]; + if ( frame >= anim->firstFrame && frame < anim->firstFrame + 6 ) { + return 1 + frame - anim->firstFrame; + } + + // change weapon + //USED TO BE TORSO_RAISE +/* if ( frame >= ci->animations[TORSO_DROPWEAP1].firstFrame + && frame < ci->animations[TORSO_DROPWEAP1].firstFrame + 9 ) { + return frame - ci->animations[TORSO_DROPWEAP1].firstFrame + 6; + } + + // stand attack + if ( frame >= ci->animations[BOTH_ATTACK3].firstFrame + && frame < ci->animations[BOTH_ATTACK3].firstFrame + 6 ) { + return 1 + frame - ci->animations[BOTH_ATTACK3].firstFrame; + } + + // stand attack 2 + if ( frame >= ci->animations[BOTH_ATTACK2].firstFrame + && frame < ci->animations[BOTH_ATTACK2].firstFrame + 6 ) { + return 1 + frame - ci->animations[BOTH_ATTACK2].firstFrame; + }*/ + + return 0; +} + +/* +============== +CG_CalculateWeaponPosition +============== +*/ +//BOOKMARK +static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) { + float scale; + int delta; + float fracsin; + + VectorCopy( cg.refdef.vieworg, origin ); + VectorCopy( cg.refdefViewAngles, angles ); + + // on odd legs, invert some angles + if ( cg.bobcycle & 1 ) { + scale = -cg.xyspeed; + } else { + scale = cg.xyspeed; + } + + // gun angles from bobbing + angles[ROLL] += scale * cg.bobfracsin * 0.005; + angles[YAW] += scale * cg.bobfracsin * 0.01; + angles[PITCH] += cg.xyspeed * cg.bobfracsin * 0.005; + + // drop the weapon when landing + delta = cg.time - cg.landTime; + if ( delta < LAND_DEFLECT_TIME ) { + origin[2] += cg.landChange*0.25 * delta / LAND_DEFLECT_TIME; + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + origin[2] += cg.landChange*0.25 * + (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME; + } + +#if 0 + // drop the weapon when stair climbing + delta = cg.time - cg.stepTime; + if ( delta < STEP_TIME/2 ) { + origin[2] -= cg.stepChange*0.25 * delta / (STEP_TIME/2); + } else if ( delta < STEP_TIME ) { + origin[2] -= cg.stepChange*0.25 * (STEP_TIME - delta) / (STEP_TIME/2); + } +#endif + + // idle drift + scale = cg.xyspeed + 40; + fracsin = sin( cg.time * 0.001 ); + angles[ROLL] += scale * fracsin * 0.01; + angles[YAW] += scale * fracsin * 0.01; + angles[PITCH] += scale * fracsin * 0.01; +} + + +/* +=============== +CG_LightningBolt + +Origin will be the exact tag point, which is slightly +different than the muzzle point used for determining hits. +The cent should be the non-predicted cent if it is from the player, +so the endpoint will reflect the simulated strike (lagging the predicted +angle) +=============== +*/ + +#define RANGE_BEAM (2048.0) +#define BEAM_VARIATION 6 + +void CG_LightningBolt( centity_t *cent, vec3_t origin ) +{ + trace_t trace; +// gentity_t *traceEnt; + vec3_t startpos, endpos, forward; + qboolean spark = qfalse, impact = qtrue; + + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) + { + return; // Don't draw a phaser during an intermission you crezzy mon! + } + + //Must be a durational weapon + if ( cent->currentState.clientNum == cg.snap->ps.clientNum && !cg.renderingThirdPerson && !(cent->currentState.eFlags & EF_ITEMPLACEHOLDER ) ) //fuck decoys + { + // different checks for first person view + if ( cg.snap->ps.weapon == WP_HYPERSPANNER || + cg.snap->ps.weapon == WP_PHASER || + cg.snap->ps.weapon == WP_DERMAL_REGEN || + (cg.snap->ps.eFlags & EF_ALT_FIRING && cg.snap->ps.weapon == WP_COMPRESSION_RIFLE ) + || (!(cg.snap->ps.eFlags & EF_ALT_FIRING) && cg.snap->ps.weapon == WP_DISRUPTOR ) + ) + { /*continue*/ } + else + return; + } else { + if ( cent->currentState.weapon == WP_HYPERSPANNER || + cent->currentState.weapon == WP_PHASER || + cent->currentState.weapon == WP_DERMAL_REGEN || + (cent->currentState.eFlags & EF_ALT_FIRING && (cent->currentState.weapon == WP_COMPRESSION_RIFLE) ) || + (!(cent->currentState.eFlags & EF_ALT_FIRING) && cent->currentState.weapon == WP_DISRUPTOR) + ) + { /*continue*/ } + else + return; + } + + // Find the impact point of the beam + if ( cent->currentState.clientNum == cg.snap->ps.clientNum + && !cg.renderingThirdPerson ) { + // take origin from view +/* + VectorCopy( cg.refdef.vieworg, origin ); + VectorMA( origin, -8, cg.refdef.viewaxis[2], origin ); + VectorMA( origin, 8, cg.refdef.viewaxis[0], origin ); + VectorMA( origin, -2, cg.refdef.viewaxis[1], origin ); +*/ + VectorCopy( cg.refdef.viewaxis[0], forward ); + VectorCopy( cg.refdef.vieworg, startpos); + } + else + { + // take origin from entity + if ( cent->currentState.clientNum == cg.snap->ps.clientNum ) + AngleVectors( cg.predictedPlayerState.viewangles, forward, NULL, NULL ); + else + AngleVectors( cent->lerpAngles, forward, NULL, NULL ); + VectorCopy( origin, startpos); + + // Check first from the center to the muzzle. + CG_Trace(&trace, cent->lerpOrigin, vec3_origin, vec3_origin, origin, cent->currentState.number, MASK_SHOT); + if (trace.fraction < 1.0) + { // We hit something here... Stomp the muzzle back to the eye... + VectorCopy(cent->lerpOrigin, startpos); + if ( cg.snap->ps.eFlags & EF_FULL_ROTATE && Q_fabs( cg.snap->ps.viewangles[PITCH] ) > 89.0f ) + startpos[2] -= 20; + else + startpos[2] += cg.snap->ps.viewheight; + } + } + + VectorMA( startpos, RANGE_BEAM, forward, endpos ); + + // Add a subtle variation to the beam weapon's endpoint + /*for (i = 0; i < 3; i ++ ) + { + endpos[i] += crandom() * BEAM_VARIATION; + }*/ + + CG_Trace( &trace, startpos, vec3_origin, vec3_origin, endpos, cent->currentState.number, MASK_SHOT ); + +// traceEnt = &g_entities[ trace.entityNum ]; + + // Make sparking be a bit less frame-rate dependent..also never add sparking when we hit a surface with a NOIMPACT flag + if (!(trace.surfaceFlags & SURF_NOIMPACT)) + { + spark = qtrue; + } + + // Don't draw certain kinds of impacts when it hits a player and such..or when we hit a surface with a NOIMPACT flag + if ( cg_entities[trace.entityNum].currentState.eType == ET_PLAYER || (trace.surfaceFlags & SURF_NOIMPACT) ) + { + impact = qfalse; + } + + // Add in the effect + switch ( cent->currentState.weapon ) + { + case WP_PHASER: + if (cg.snap->ps.rechargeTime == 0) + { + if ( cent->currentState.eFlags & EF_ALT_FIRING ) + FX_PhaserAltFire( origin, trace.endpos, trace.plane.normal, spark, impact, cent->pe.empty ); + else + FX_PhaserFire( origin, trace.endpos, trace.plane.normal, spark, impact, cent->pe.empty ); + } + break; + case WP_COMPRESSION_RIFLE: + if ( cent->currentState.eFlags & EF_ALT_FIRING ) + { + FX_PrifleBeamFire( origin, trace.endpos, trace.plane.normal, spark, impact, cent->pe.empty ); + } + break; + case WP_HYPERSPANNER: + if ( cent->currentState.eFlags & EF_ALT_FIRING ) + FX_ProbeBeam( origin, forward, cent->currentState.clientNum, qtrue ); + else + FX_ProbeBeam( origin, forward, cent->currentState.clientNum, qfalse ); + break; + + case WP_DERMAL_REGEN: + if ( cent->currentState.eFlags & EF_ALT_FIRING ) + FX_RegenBeam( origin, forward, cent->currentState.clientNum, qtrue ); + else + FX_RegenBeam( origin, forward, cent->currentState.clientNum, qfalse ); + break; + + case WP_DISRUPTOR: + if ( cent->currentState.eFlags & EF_FIRING && !(cent->currentState.eFlags & EF_ALT_FIRING) ) + FX_DisruptorBeamFire( origin, trace.endpos, trace.plane.normal, spark, impact, cent->pe.empty ); + +/* case WP_DERMAL_REGEN: + if (!(cent->currentState.eFlags & EF_ALT_FIRING)) + { + vec3_t org; + + // Move the beam back a bit to help cover up the poly edges on the fire beam + VectorMA( origin, -4, forward, org ); + FX_DreadnoughtFire( org, trace.endpos, trace.plane.normal, spark, impact ); + } + break;*/ + } +} + + +/* +====================== +CG_MachinegunSpinAngle +====================== +*/ +#define SPIN_SPEED 0.9 +#define COAST_TIME 1000 +static float CG_MachinegunSpinAngle( centity_t *cent ) { + int delta; + float angle; + float speed; + + delta = cg.time - cent->pe.barrelTime; + if ( cent->pe.barrelSpinning ) { + angle = cent->pe.barrelAngle + delta * SPIN_SPEED; + } else { + if ( delta > COAST_TIME ) { + delta = COAST_TIME; + } + + speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME ); + angle = cent->pe.barrelAngle + delta * speed; + } + + if ( cent->pe.barrelSpinning == !(cent->currentState.eFlags & EF_FIRING) ) { + cent->pe.barrelTime = cg.time; + cent->pe.barrelAngle = AngleMod( angle ); + cent->pe.barrelSpinning = !!(cent->currentState.eFlags & EF_FIRING); + } + + return angle; +} + + +/* +======================== +CG_AddWeaponWithPowerups +======================== +*/ + +static void CG_AddWeaponWithPowerups( refEntity_t *gun, int powerups, beamData_t* beamData, int cloakTime, int decloakTime ) // +{ + // add powerup effects + if ( powerups & ( 1 << PW_INVIS ) || ( !(powerups & ( 1 << PW_INVIS )) && decloakTime > 0 ) ) { + + //TiM - modified so it persists during the first bit of cloaking / last of decloaking + if ( ( cloakTime <= 0 && decloakTime <= 0 ) || ( decloakTime > 0 && cg.time < ( decloakTime + Q_FLASH_TIME * 0.5 ) ) + || ( cloakTime > 0 && cg.time > ( cloakTime + Q_FLASH_TIME * 0.5 ) ) ) + { + if ( /*cg.snap->ps.persistant[PERS_CLASS] == PC_ADMIN*/ cgs.clientinfo[cg.snap->ps.clientNum].isAdmin ) + {//admins can see cloaked people + //gun->customShader = cgs.media.teleportEffectShader; + //TiM - Make it look cooler - Half invis + gun->renderfx |= RF_FORCE_ENT_ALPHA; + gun->shaderRGBA[3] = (unsigned char)(0.4f * 255.0f); + trap_R_AddRefEntityToScene( gun ); + } + } + else + trap_R_AddRefEntityToScene( gun ); + + //gun->customShader = cgs.media.invisShader; + //trap_R_AddRefEntityToScene( gun ); + } + else if ( powerups & ( 1 << PW_BEAM_OUT ) || powerups & ( 1 << PW_QUAD ) ) + { + int btime; + btime = cg.time - beamData->beamTimeParam; + + if ( btime <= PLAYER_BEAM_FADE ) { + if ( powerups & ( 1 << PW_BEAM_OUT ) ) { + gun->shaderRGBA[3] = 255; + } + else { + gun->shaderRGBA[3] = 0; + } + } + else if ( btime >= ( PLAYER_BEAM_FADE + PLAYER_BEAM_FADETIME ) ) { + if ( powerups & ( 1 << PW_BEAM_OUT ) ) { + gun->shaderRGBA[3] = 0; + } + else { + gun->shaderRGBA[3] = 255; + } + } + + if (btime > PLAYER_BEAM_FADE && btime < (PLAYER_BEAM_FADE + PLAYER_BEAM_FADETIME) ) + { + gun->renderfx |= RF_FORCE_ENT_ALPHA; + gun->shaderRGBA[3] = (int)(255 * beamData->beamAlpha); + } + + if ( gun->shaderRGBA[3] > 0 ) { + trap_R_AddRefEntityToScene( gun ); + gun->renderfx &= ~RF_FORCE_ENT_ALPHA; + gun->shaderRGBA[3] = 255; + } + + //Just a precaution. Loop it once, then the player should be invisible + if ( btime < 4100 ) { + gun->customShader = cgs.media.transportShader; + gun->shaderTime = beamData->beamTimeParam * 0.001; + trap_R_AddRefEntityToScene( gun ); + } + } else if(powerups & (1 << PW_BORG_ADAPT)) { + gun->renderfx |= RF_FORCE_ENT_ALPHA; + gun->shaderRGBA[3] = 255; + trap_R_AddRefEntityToScene(gun); + gun->customShader = cgs.media.borgFullBodyShieldShader; + trap_R_AddRefEntityToScene(gun); + return; + } + else { + trap_R_AddRefEntityToScene( gun ); + + if(gun->renderfx & RF_FORCE_ENT_ALPHA) { + gun->renderfx &= ~RF_FORCE_ENT_ALPHA; + } + +/* if ( powerups & ( 1 << PW_BOLTON ) ) { + gun->customShader = cgs.media.battleWeaponShader; + trap_R_AddRefEntityToScene( gun ); + }*/ + +/* if ( powerups & ( 1 << PW_QUAD ) ) { + gun->customShader = cgs.media.quadWeaponShader; + trap_R_AddRefEntityToScene( gun ); + }*/ + /*if (powerups & (1 << PW_OUCH)) + { + gun->customShader = cgs.media.holoOuchShader; + // set rgb to 1 of 16 values from 0 to 255. don't use random so that the three + //parts of the player model as well as the gun will all look the same + gun->shaderRGBA[0] = + gun->shaderRGBA[1] = + gun->shaderRGBA[2] = ((cg.time % 17)*0.0625)*255;//irandom(0,255); + trap_R_AddRefEntityToScene(gun); + }*/ + } +} + +/*void CG_CoffeeSteamFirstPerson ( refEntity_t* parent, weaponInfo_t *weapon ) { + refEntity_t steam; + vec3_t angle = { 0.0, 0.0, 6.0 }; + + CG_PositionEntityOnTag( &steam, parent, weapon->viewModel, "tag_steam1" ); + + if ( VectorCompare( steam.origin, parent->origin ) ) {//whelp, for some whacky reason, there's no tag O_o + return; + } + + //CG_Steam( steam.origin, angle); + //Disables the OpenGL Hack where the viewmodel is drawn over EVERYTHING ELSE INCLUDING THE STEAM + parent->renderfx &= ~RF_DEPTHHACK; + + if (cg.time % 10 == 0 ) { + FX_AddSprite( steam.origin, + angle, qfalse, + ( random() * 3 + 1), (10), //random() * 4 + 2 //12 + 0.6 + random() * 0.4, 0.0, + random() * 120, //180 + 0.0, + 1300, //300 //random() * 200 + 1200, //300 // + cgs.media.steamShader ); + } + /*localEntity_t *FX_AddSprite(vec3_t origin, vec3_t velocity, qboolean gravity, float scale, float dscale, + float startalpha, float endalpha, float roll, float elasticity, + float killTime, qhandle_t shader)*/ + + //if ( +/*} + +void CG_CoffeeSteamThirdPerson ( refEntity_t* parent, weaponInfo_t *weapon) { + refEntity_t steam; + localEntity_t *le = NULL; + + vec3_t angle = { 0.0, 0.0, 6.0 }; + + CG_PositionEntityOnTag( &steam, parent, weapon->weaponModel, "tag_steam"); + + if ( VectorCompare( steam.origin, parent->origin ) ) {//whelp, for some whacky reason, there's no tag O_o + return; + } + + if (cg.time % 10 == 0 ) { + le = FX_AddSprite( steam.origin, + angle, qfalse, + ( random() * 1.2 + 0.5), ( 5 ), //random() * 4 + 2 //12 + 0.6 + random() * 0.4, 0.0, + random() * 120, //180 + 0.0, + 1300, //300 //random() * 200 + 1200, //300 // + cgs.media.steamShader ); + } + /*localEntity_t *FX_AddSprite(vec3_t origin, vec3_t velocity, qboolean gravity, float scale, float dscale, + float startalpha, float endalpha, float roll, float elasticity, + float killTime, qhandle_t shader)*/ + + //if ( +//}*/ + +void CG_CoffeeSteam( refEntity_t* parent, weaponInfo_t *weapon, qboolean thirdperson ) { + refEntity_t steam; + localEntity_t *le; + + vec3_t angle = { 0.0, 0.0, 10.0 }; + + //FIXME: I probably should name the tag the same thing in both models... O_o + if ( !thirdperson ) { + CG_PositionEntityOnTag( &steam, parent, weapon->viewModel, "tag_steam1" ); + } + else { + CG_PositionEntityOnTag( &steam, parent, weapon->weaponModel, "tag_steam"); + } + + if ( VectorCompare( steam.origin, parent->origin ) ) {//whelp, for some whacky reason, there's no tag O_o + return; + } + + //CG_Steam( steam.origin, angle); + //Disables the OpenGL Hack where the viewmodel is drawn over EVERYTHING ELSE INCLUDING THE STEAM + parent->renderfx &= ~RF_DEPTHHACK; + + if (cg.time % 10 == 0 ) { //release a sprite every .01 of a second + le = FX_AddSprite( steam.origin, + angle, qfalse, + ( thirdperson ? random() * 1.2 + 0.5 : random() * 1 + 1), ( thirdperson ? 7 : 10), //random() * 4 + 2 //12 + 0.05 + random() * 0.1, 0.0, + random() * 120, //180 + 0.0, + 1500, //300 //random() * 200 + 1200, //300 // + cgs.media.steamShader ); + } + /*localEntity_t *FX_AddSprite(vec3_t origin, vec3_t velocity, qboolean gravity, float scale, float dscale, + float startalpha, float endalpha, float roll, float elasticity, + float killTime, qhandle_t shader)*/ + //Without this, the steam gets drawn behind the cup... which looks weird + //le->refEntity.renderfx |= RF_DEPTHHACK; +} + +/* +============= +getClassColor + +RPG-X : TiM - used to determine what color skins the weapons should have applied to them +My way of having to not have to enter in so many conditionals over and over +============= +*/ + +//char *getClassColor ( void ) +//{ +// /*if (( cg.snap->ps.persistant[PERS_CLASS] == PC_SECURITY ) +// || ( cg.snap->ps.persistant[PERS_CLASS] == PC_ENGINEER)) +// { +// return "default"; +// } +// +// if (( cg.snap->ps.persistant[PERS_CLASS] == PC_SCIENCE ) +// || ( cg.snap->ps.persistant[PERS_CLASS] == PC_MEDICAL ) +// || ( cg.snap->ps.persistant[PERS_CLASS] == PC_ALPHAOMEGA22 )) +// { +// return "teal"; +// } +// +// if ((cg.snap->ps.persistant[PERS_CLASS] == PC_COMMAND) +// || (cg.snap->ps.persistant[PERS_CLASS] == PC_ADMIN)) +// { +// return "red"; +// } +// if ( cg.snap->ps.persistant[PERS_CLASS] == PC_NOCLASS ) { +// return "NULL"; +// }*/ +// +// //lolz... this time, let's base it off of current model +// //rather than class +// cgs.clientinfo[0]. +// +// return "default"; +//} + + +/* +============= +CG_AddPlayerWeapon + +Used for both the view weapon (ps is valid) and the world modelother character models (ps is NULL) +The main player will have this called for BOTH cases, so effects like light and +sound should only be done on the world model case. +============= +*/ +void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent ) { + refEntity_t gun; + refEntity_t barrel; + refEntity_t flash; + vec3_t angles; + weapon_t weaponNum; + weaponInfo_t *weapon; + centity_t *nonPredictedCent; + int i = 0, numBarrels = 0; + wpnBarrelInfo_t *barrelInfo = NULL; + + char filename[MAX_QPATH]; + char* skinColor; + + weaponNum = cent->currentState.weapon; + + CG_RegisterWeapon( weaponNum ); + weapon = &cg_weapons[weaponNum]; + + // add the weapon + memset( &gun, 0, sizeof( gun ) ); + VectorCopy( parent->lightingOrigin, gun.lightingOrigin ); + gun.shadowPlane = parent->shadowPlane; + gun.renderfx = parent->renderfx; + + // set custom shading for railgun refire rate + /*if ( ps ) { + if ( cg.predictedPlayerState.weapon == WP_NULL_HAND + && cg.predictedPlayerState.weaponstate == WEAPON_FIRING ) { + float f; + + f = (float)cg.predictedPlayerState.weaponTime / 1500; + gun.shaderRGBA[1] = 0; + gun.shaderRGBA[0] = + gun.shaderRGBA[2] = 255 * ( 1.0 - f ); + } else { + gun.shaderRGBA[0] = 255; + gun.shaderRGBA[1] = 255; + gun.shaderRGBA[2] = 255; + gun.shaderRGBA[3] = 255; + } + }*/ + + if (ps) + { + qhandle_t skin; + + gun.hModel = weapon->viewModel; + + skinColor = cgs.clientinfo[cg.snap->ps.clientNum].skinName; + + //if ( skinColor != "NULL" ) { //RPG-X : TiM - Will change the color of the band on the viewmodel's arm, depending what class + if(!Q_stricmpn(skinColor, "NULL", 4)) { + for ( i = 0; i < 8; i++ ) { + if ( cg.predictedPlayerState.weapon == (RAweapons[i]) ) { + Com_sprintf( filename, sizeof( filename ),"models/weapons2/%s/model_%s.skin", RAweapFileName[i], skinColor ); //Formulate the skin route + + skin = trap_R_RegisterSkin ( filename ); + + if ( !skin ) + break; + + gun.customSkin = skin; //and 'plonk' it on the model :) + break; + } + } + } + + } + else + { + gun.hModel = weapon->weaponModel; + } + + if (!gun.hModel) { + return; + } + + if ( !ps ) { + // add weapon stop sound + if ( !( cent->currentState.eFlags & EF_FIRING ) && !( cent->currentState.eFlags & EF_ALT_FIRING ) && cent->pe.lightningFiring && + cg.predictedPlayerState.ammo[cg.predictedPlayerState.weapon] ) + { + if (weapon->stopSound) + { + trap_S_StartSound( cent->lerpOrigin, cent->currentState.number, CHAN_WEAPON, weapon->stopSound ); + } + else if (weapon->altStopSound ) + { + trap_S_StartSound( cent->lerpOrigin, cent->currentState.number, CHAN_WEAPON, weapon->altStopSound ); + } + } + cent->pe.lightningFiring = qfalse; + if ( cent->currentState.eFlags & EF_ALT_FIRING ) + { + // hark, I smell hackery afoot + if ((weaponNum == WP_PHASER) && !(cg.predictedPlayerState.ammo[WP_PHASER])) + { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.phaserEmptySound ); + cent->pe.lightningFiring = qtrue; + } + else if ( weapon->altFiringSound && !weapon->isAnimSndBased ) + { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->altFiringSound ); + cent->pe.lightningFiring = qtrue; + } + + if ( weaponNum == WP_TOOLKIT || weaponNum == WP_MEDKIT ) { + cent->pe.lightningFiring = qtrue; + } + } + else if ( cent->currentState.eFlags & EF_FIRING ) + { + if ((weaponNum == WP_PHASER) && !(cg.predictedPlayerState.ammo[WP_PHASER])) + { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.phaserEmptySound ); + cent->pe.lightningFiring = qtrue; + } + else if ( weapon->firingSound && !weapon->isAnimSndBased ) + { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->firingSound ); + cent->pe.lightningFiring = qtrue; + } + + //TiM: Haxxor. I want the medkit + toolkit sounds to play only once when u hold them down + if ( weaponNum == WP_TOOLKIT || weaponNum == WP_MEDKIT ) { + cent->pe.lightningFiring = qtrue; + } + } + } + + + CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon"); + //RPG-X : TiM - A little variety here :) Toolkit gets attached to player model's left hand, medkit on waist :) + //Hack: I dunno why, but unless I specified thirdperson (ie (!ps) ), the viewmodel went crazy. :P + if (!ps) { + if (( weaponNum == WP_TOOLKIT ) ) { //Toolkit //cg.predictedPlayerState.weapon + CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_lhand"); + } + else if (( weaponNum == WP_MEDKIT ) ) { //Medkit + CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_torso"); + } + /*else { + CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon"); + }*/ + //TiM: also in the hopes of keeping the weapon scale constant in contrast to the player model + gun.nonNormalizedAxes = qfalse; + } + + if ( weaponNum == WP_COFFEE ) { + if ( !ps ) { + if ( !(!cg.renderingThirdPerson && cent->currentState.clientNum == cg.predictedPlayerState.clientNum) ) + CG_CoffeeSteam( &gun, weapon, qtrue ); + } + //else { + // CG_CoffeeSteam( &gun, weapon, qfalse ); + //} + } + + CG_AddWeaponWithPowerups( &gun, cent->currentState.powerups, ¢->beamData, cent->cloakTime, cent->decloakTime ); + + // add the spinning barrel + // + // + for (barrelInfo = wpnBarrelData; barrelInfo->giTag != WP_NONE; barrelInfo++) + { + if (barrelInfo->giTag == weaponNum) + { + numBarrels = barrelInfo->numBarrels; + break; + } + } + + // don't add barrels to world model...only viewmodels + if (ps) + { + for (i = 0; i < numBarrels; i++) + { + memset( &barrel, 0, sizeof( barrel ) ); + VectorCopy( parent->lightingOrigin, barrel.lightingOrigin ); + barrel.shadowPlane = parent->shadowPlane; + barrel.renderfx = parent->renderfx; + + barrel.hModel = weapon->barrelModel[i]; + angles[YAW] = 0; + angles[PITCH] = 0; + if ( weaponNum == WP_TR116) { + angles[ROLL] = CG_MachinegunSpinAngle( cent ); + } else { + angles[ROLL] = 0;//CG_MachinegunSpinAngle( cent ); + } + AnglesToAxis( angles, barrel.axis ); + + if (!i) { + CG_PositionRotatedEntityOnTag( &barrel, parent, weapon->handsModel, "tag_barrel" ); + } else { + CG_PositionRotatedEntityOnTag( &barrel, parent, weapon->handsModel, va("tag_barrel%d",i+1) ); + } + + CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups, ¢->beamData, cent->cloakTime, cent->decloakTime ); + } + } + + // make sure we aren't looking at cg.predictedPlayerEntity for LG + nonPredictedCent = &cg_entities[cent->currentState.clientNum]; + + // if the index of the nonPredictedCent is not the same as the clientNum + // then this is a fake player (like on teh single player podiums), so + // go ahead and use the cent + if( ( nonPredictedCent - cg_entities ) != cent->currentState.clientNum ) { + nonPredictedCent = cent; + } + + //Com_Printf("eType: %i, eventParm: %i, weaponNum: %i\n", cent->currentState.eType, cent->currentState.eventParm, weaponNum); + if ( weaponNum == WP_COMPRESSION_RIFLE + && + cent->currentState.powerups & ( 1 << PW_FLASHLIGHT ) + && + cent->beamData.beamTimeParam == 0 + && + ( !(cent->currentState.powerups & ( 1 << PW_INVIS )) + || cent->currentState.clientNum == cg.predictedPlayerState.clientNum ) + ) + { //FIXME: TiM - need to know if flashlight is on or off at the time :S + refEntity_t flashlight; + + memset( &flashlight, 0, sizeof( flashlight ) ); + VectorCopy( parent->lightingOrigin, flashlight.lightingOrigin ); + flashlight.shadowPlane = parent->shadowPlane; + flashlight.renderfx = parent->renderfx; + + flashlight.hModel = cgs.media.flashlightModel; + if (!flashlight.hModel) { + return; + } + + angles[YAW] = 0; + angles[PITCH] = 0; + angles[ROLL] = 0; + + AnglesToAxis( angles, flashlight.axis ); + + if (ps) + { // Rendering inside the head... + CG_PositionRotatedEntityOnTag( &flashlight, &gun, weapon->viewModel, "tag_flashlight"); + } + else + { // Rendering outside the head... + CG_PositionRotatedEntityOnTag( &flashlight, &gun, weapon->weaponModel, "tag_flashlight"); + } + trap_R_AddRefEntityToScene( &flashlight ); + } + + // add the flash + if ( ( weaponNum == WP_PHASER || + weaponNum == WP_DERMAL_REGEN) + && ( nonPredictedCent->currentState.eFlags & EF_FIRING ) ) + { + // continuous flash + } + else + { + // impulse flash + //if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME) { + if ( cg.time - cent->muzzleFlashTime > wpnBarrelData[weaponNum-1].flashTime ) + { + return; + } + } + + memset( &flash, 0, sizeof( flash ) ); + VectorCopy( parent->lightingOrigin, flash.lightingOrigin ); + flash.shadowPlane = parent->shadowPlane; + flash.renderfx = parent->renderfx; + + flash.hModel = weapon->flashModel; + if (!flash.hModel) { + return; + } + + angles[YAW] = 0; + angles[PITCH] = 0; + angles[ROLL] = 0; //angles[ROLL] = crandom() * 10; //RPG-X - TiM: This stops the lensflare on the muzzle from jiggling around + + AnglesToAxis( angles, flash.axis ); + + //TiM - Instead of briefly showing the flash, show it scaling down + if (weaponNum != WP_PHASER && + weaponNum != WP_HYPERSPANNER && + weaponNum != WP_DERMAL_REGEN && + !(weaponNum == WP_COMPRESSION_RIFLE && (cent->currentState.eFlags & EF_ALT_FIRING) ) && + !(weaponNum == WP_DISRUPTOR && !(cent->currentState.eFlags & EF_ALT_FIRING) ) + ) + { + float scale; + scale = (1.0f - ( (float)(cg.time - cent->muzzleFlashTime) / (float)wpnBarrelData[weaponNum-1].flashTime )) * 2.0f; + + flash.nonNormalizedAxes = qtrue; + VectorScale( flash.axis[0], scale, flash.axis[0] ); + VectorScale( flash.axis[1], scale, flash.axis[1] ); + VectorScale( flash.axis[2], scale, flash.axis[2] ); + } + + //TiM - quick hack + //jiggle the scale of the phaser rifle on alt fire around + if ( (weaponNum == WP_COMPRESSION_RIFLE && (cent->currentState.eFlags & EF_ALT_FIRING)) + || + ( weaponNum == WP_DISRUPTOR && !(cent->currentState.eFlags & EF_ALT_FIRING)) ) + { + float min, max; + + if ( weaponNum == WP_COMPRESSION_RIFLE ) + { + min = 1.3f; + max = 1.6f; + } + else + { + min = 0.8f; + max = 0.9f; + } + + VectorScale( flash.axis[0], flrandom(min, max), flash.axis[0] ); + VectorScale( flash.axis[1], flrandom(min, max), flash.axis[1] ); + VectorScale( flash.axis[2], flrandom(min, max), flash.axis[2] ); + } + + if (cent->pe.empty) + { // Make muzzle flash wussy when empty. + flash.customShader = cgs.media.phaserMuzzleEmptyShader; + } + + if (ps) + { // Rendering inside the head... + CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->viewModel, "tag_flash" ); + } + else + { // Rendering outside the head... + CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->weaponModel, "tag_flash" ); + } + + if ( !(cent->currentState.powerups & ( 1 << PW_INVIS )) + || cent->currentState.clientNum == cg.predictedPlayerState.clientNum ) + { + trap_R_AddRefEntityToScene( &flash ); + } + + if ( ps || cg.renderingThirdPerson || cent->currentState.number != cg.predictedPlayerState.clientNum || cg_firstPersonBody.integer ) + { + // add phaser/dreadnought + // grrr nonPredictedCent doesn't have the proper empty setting + nonPredictedCent->pe.empty = cent->pe.empty; + CG_LightningBolt( nonPredictedCent, flash.origin ); + + // make a dlight for the flash + if ( (weapon->flashDlightColor[0] || weapon->flashDlightColor[1] || weapon->flashDlightColor[2]) && !(cent->currentState.powerups & ( 1 << PW_INVIS ) ) ) { + trap_R_AddLightToScene( flash.origin, 200 + (rand()&31), weapon->flashDlightColor[0], + weapon->flashDlightColor[1], weapon->flashDlightColor[2] ); + } + } +} + +/* +============== +CG_AddViewWeapon + +Add the weapon, and flash for the player's view +============== +*/ +void CG_AddViewWeapon( playerState_t *ps ) { + refEntity_t hand; + centity_t *cent; + clientInfo_t *ci; + float fovOffset; + vec3_t angles; + weaponInfo_t *weapon; + + if ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR /*|| (ps->eFlags&EF_ELIMINATED)*/ ) { + return; + } + + if ( ps->pm_type == PM_INTERMISSION ) { + return; + } + + // no gun if in third person view + if ( cg.renderingThirdPerson || cg_firstPersonBody.integer ) { + return; + } + + // allow the gun to be completely removed + //TiM: Added alt fire for alt-fire beam weapons + if ( !cg_drawGun.integer || cg.zoomed ) { + vec3_t origin; + + if ( cg.predictedPlayerState.eFlags & EF_FIRING || cg.predictedPlayerState.eFlags & EF_ALT_FIRING ) + { + // special hack for phaser/dreadnought... + VectorCopy( cg.refdef.vieworg, origin ); + VectorMA( origin, -8, cg.refdef.viewaxis[2], origin ); + CG_LightningBolt( &cg_entities[ps->clientNum], origin ); + } + return; + } + + if ( (cg.zoomed) && (ps->weapon == WP_COMPRESSION_RIFLE) ) { //RPG-X : TiM - People were saying that being able to see the gunsight on the rifle thru the gunsight in zoom mode was weird :P + return; + } + + // don't draw if testing a gun model + if ( cg.testGun ) { + return; + } + + // drop gun lower at higher fov + if ( cg_fov.integer > 80 ) { + fovOffset = -0.2 * ( cg_fov.integer - 80 ); + } else { + fovOffset = 0; + } + + cent = &cg.predictedPlayerEntity; // &cg_entities[cg.snap->ps.clientNum]; + CG_RegisterWeapon( ps->weapon ); + weapon = &cg_weapons[ ps->weapon ]; + + memset (&hand, 0, sizeof(hand)); + + // set up gun position + CG_CalculateWeaponPosition( hand.origin, angles ); + + VectorMA( hand.origin, cg_gun_x.value, cg.refdef.viewaxis[0], hand.origin ); + VectorMA( hand.origin, cg_gun_y.value, cg.refdef.viewaxis[1], hand.origin ); + VectorMA( hand.origin, (cg_gun_z.value+fovOffset), cg.refdef.viewaxis[2], hand.origin ); + + AnglesToAxis( angles, hand.axis ); + + // map torso animations to weapon animations + if ( cg_gun_frame.integer ) { + // development tool + hand.frame = hand.oldframe = cg_gun_frame.integer; + hand.backlerp = 0; + } else { + // get clientinfo for animation map + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + hand.frame = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.frame ); + hand.oldframe = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.oldFrame ); + hand.backlerp = cent->pe.torso.backlerp; + } + + hand.hModel = weapon->handsModel; + hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON; + + // add everything onto the hand + CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity ); +} + +/* +============================================================================== + +WEAPON SELECTION + +============================================================================== +*/ + +void static CG_RegisterWeaponIcon( int weaponNum ) { + weaponInfo_t *weaponInfo; + gitem_t *item; + + weaponInfo = &cg_weapons[weaponNum]; + + if ( weaponNum == 0 ) { + return; + } + + if ( weaponInfo->registered ) { + return; + } + + for ( item = bg_itemlist + 1 ; item->classname ; item++ ) { + if ( item->giType == IT_WEAPON && item->giTag == weaponNum ) { + weaponInfo->item = item; + break; + } + } + if ( !item->classname ) { + CG_Error( "Couldn't find weapon %i", weaponNum ); + } + + weaponInfo->weaponIcon = trap_R_RegisterShader( item->icon ); +} + +/* +================== +CG_DrawWeaponIcon +RPG-X | Phenix | 08/06/2005 +RPG-X | TiM | 5/1/2006 +=========================== +*/ +void CG_DrawWeaponIcon ( int x, int y, int weapon ) +{ + /*vec4_t color; + + color[3] = alpha; + if ( !color[3] ) { + return; + }*/ + + CG_RegisterWeaponIcon( weapon ); //short version + + // draw selection marker + + if ( weapon == cg.weaponSelect ) + { + trap_R_SetColor( colorTable[CT_LTPURPLE1] ); + } + else + { + trap_R_SetColor(colorTable[CT_DKPURPLE1]); + } + + CG_DrawPic( x-4,y-4,38, 38, cgs.media.weaponbox); + + // draw weapon icon + trap_R_SetColor(colorTable[CT_WHITE]); + CG_DrawPic( x, y, 32, 32, cg_weapons[weapon].weaponIcon ); + + // draw selection marker + /*if ( weapon == cg.weaponSelect ) { + CG_DrawPic( x-4, y-4, 40, 40, cgs.media.selectShader );*/ + //} + trap_R_SetColor( NULL ); +} + +/* +=================== +CG_DrawWeaponSelect +=================== +*/ + +static int weaponRows[6][3] = { WP_NULL_HAND, 0, 0, + WP_TRICORDER, WP_PADD, WP_COFFEE, + WP_PHASER, WP_COMPRESSION_RIFLE, WP_TR116, + WP_GRENADE_LAUNCHER, WP_QUANTUM_BURST, WP_DISRUPTOR, + WP_MEDKIT, WP_VOYAGER_HYPO, WP_DERMAL_REGEN, + WP_TOOLKIT, WP_HYPERSPANNER, 0 }; + +void CG_DrawWeaponSelect( void ) { + int i, rowCount, cellCount; + int bits; + //int count; + int x, y, w, defaultX, defaultY; + char *name; + float *color; + qboolean WeapOnThisRow = qfalse; + //vec4_t color; + + // don't display if dead + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 1 || cg.predictedPlayerState.eFlags & EF_DEAD ) { //RPG-X: RedTechie - No weapons at health 1 (you die at health 1 now) + return; + } + + color = CG_FadeColor( cg.weaponSelectTime, WEAPON_SELECT_TIME ); + if ( !color ) { + return; + } + + // showing weapon select clears pickup item display, but not the blend blob + cg.itemPickupTime = 0; + + // count the number of weapons owned + bits = cg.snap->ps.stats[ STAT_WEAPONS ]; + + //NEW HUD FOR RPG-X + defaultX = 18; + defaultY = 52; + x = defaultX; + + y = (BIGCHAR_HEIGHT * 2) + 20; + + for ( i = 0, rowCount = 0, cellCount = 0; i < MAX_WEAPONS; i++, cellCount++ ) { + if ( cellCount == 3 ) { //we've hit the end of the row + rowCount++; //go to the next row + cellCount = 0; //reset cell clock + + if ( WeapOnThisRow ) { + //**** Draw the end caps ***** + //VectorCopy( colorTable[CT_LTPURPLE2], color ); + trap_R_SetColor(colorTable[CT_LTPURPLE2]); + // Left end cap + CG_DrawPic( 2, y - 5, 16, 50, cgs.media.weaponcap1); //6 + // Right End Cap + CG_DrawPic( x - 20 + 16, y - 5, 16, 50, cgs.media.weaponcap2); //2 - 6, 16 - 18 + trap_R_SetColor(NULL); + + y += defaultY; + x = defaultX; + + WeapOnThisRow = qfalse; + } + + if ( rowCount >= 6 ) { //if we exceed our rows, that's bad O_o + break; + } + } + + if ( weaponRows[rowCount][cellCount] == 0 ) { + i--; + continue; + } + + if (bits & ( 1 << weaponRows[rowCount][cellCount] ) ) { + CG_DrawWeaponIcon( x, y, weaponRows[rowCount][cellCount] ); + x += 40; + + if ( !WeapOnThisRow ) { + WeapOnThisRow = qtrue; + } + } + } + + // END HUD + + // draw the selected names + if ( cg_weapons[ cg.weaponSelect ].item ) { + name = cg_weapons[ cg.weaponSelect ].item->pickup_name; + if ( name ) { + w= UI_ProportionalStringWidth(name,UI_SMALLFONT); + UI_DrawProportionalString(x, y, name, UI_SMALLFONT,color); + + } + } + + trap_R_SetColor( NULL ); +} + + +/* +=============== +CG_WeaponSelectable +=============== +*/ +static qboolean CG_WeaponSelectable( int i ) { + if ( !cg.snap->ps.ammo[i] ) { + return qfalse; + } + if ( ! (cg.snap->ps.stats[ STAT_WEAPONS ] & ( 1 << i ) ) ) { + return qfalse; + } + + return qtrue; +} + +extern int altAmmoUsage[]; +/* +{ + 0, //WP_NONE, + 2, //WP_PHASER, + 10, //WP_COMPRESSION_RIFLE, + 3, //WP_NULL_HAND, + 5, //WP_COFFEE, + 1, //WP_DISRUPTOR, + 1, //WP_GRENADE_LAUNCHER, + 2, //WP_TR116, + 2, //WP_QUANTUM_BURST, + 5 //WP_DERMAL_REGEN, + 20, //WP_VOYAGER_HYPO, + ##, //WP_TOOLKIT, + ##, //WP_MEDKIT, + +}; +*/ + +/* +=============== +CG_WeaponAltSelectable +=============== +*/ +static qboolean CG_WeaponAltSelectable( int i ) { + if ( cg.snap->ps.ammo[i] < altAmmoUsage[cg.snap->ps.weapon]) { + return qfalse; + } + if ( ! (cg.snap->ps.stats[ STAT_WEAPONS ] & ( 1 << i ) ) ) { + return qfalse; + } + + return qtrue; +} + + +/* +=============== +CG_NextWeapon_f +=============== +*/ +void CG_NextWeapon_f( void ) { + int i; //, topWeapon + int original; +// int newWeapons[16]; +// int bits; + + if ( !cg.snap ) { + return; + } + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + cg.weaponSelectTime = cg.time; + original = cg.weaponSelect; + + + //RPG-X | Phenix | 08/06/2005 + //Removed to be replaced to scroll through our list + //TiM | 4/1/2006 + //Put back in since I optimized the way weapons are handled + for ( i = 0 ; i < 16 ; i++ ) { + cg.weaponSelect++; + if ( cg.weaponSelect == 16 ) { + cg.weaponSelect = 0; + } + if ( CG_WeaponSelectable( cg.weaponSelect ) ) { + break; + } + } + if ( i == 16 ) { + cg.weaponSelect = original; + } + + //TiM: Just for the record. Phenix. Enumerated value lists. Look them up. Use them! + //Reading this code was really tricky when it didn't have to be >.< + //ie 1 = WP_PHASER etc +} + +/* +=============== +CG_PrevWeapon_f +=============== +*/ +void CG_PrevWeapon_f( void ) { + int i; //, topWeapon + int original; +// int newWeapons[16]; +// int bits; + + if ( !cg.snap ) { + return; + } + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + cg.weaponSelectTime = cg.time; + original = cg.weaponSelect; + + //RPG-X | Phenix | 08/06/2005 + //Removed to be replaced to scroll through our list + //TiM | 4/1/2006 + //Put back in since I optimized the way weapons are handled + for ( i = 0 ; i < 16 ; i++ ) { + cg.weaponSelect--; + if ( cg.weaponSelect == -1 ) { + cg.weaponSelect = 15; + } + if ( CG_WeaponSelectable( cg.weaponSelect ) ) { + break; + } + } + if ( i == 16 ) { + cg.weaponSelect = original; + } +} + +/* +=============== +CG_Weapon_f +=============== +*/ +/*TiM : Here for reference +static int weaponRows[6][3] = { WP_NULL_HAND, 0, 0, + WP_TRICORDER, WP_PADD, WP_COFFEE, + WP_PHASER, WP_COMPRESSION_RIFLE, WP_TR116, + WP_GRENADE_LAUNCHER, WP_QUANTUM_BURST, WP_DISRUPTOR, + WP_MEDKIT, WP_VOYAGER_HYPO, WP_DERMAL_REGEN, + WP_TOOLKIT, WP_NEUTRINO_PROBE, 0 };*/ + +void CG_Weapon_f( void ) { + int num; + //int newWeapons[16]; + int i; + int bits; + int weaponsOnRow; + int weaponGot[6]; + int onRow; + int onCol; + int rowsUsed; + int currentWeaponCol; + + if ( !cg.snap ) { + return; + } + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + num = atoi( CG_Argv( 1 ) ); + bits = cg.snap->ps.stats[ STAT_WEAPONS ]; + + //TiM - 0 = Null hand weapon now + //if ( num < 1 || num > 15 ) { + if ( num < 0 || num > 15 ) { + return; + } + + cg.weaponSelectTime = cg.time; + + //Hacky Override: 0 = Null hand no matter what. + if (num == 0 ) { + if ( bits & ( 1 << WP_NULL_HAND ) ) { + cg.weaponSelect = WP_NULL_HAND; + } + return; + } + + //TiM : The code below went into an infinite loop if a high number was + //set as an arg to this command. + //Lemme just insert a check to make sure the code NEVER accepts args higher + //than the size of our weapons array. I'll put it underneath the weaponSelectTime + //statement, so the user will still see a response to their input. + else if ( num > 5 ) { + return; + } + + /* RPG-X | Phenix | 02/02/2006 + * + * Code to group weapons together by keyboard */ + + //Init weaponGot values + /*for (i = 0; i < 6; i++) + weaponGot[i] = -1;*/ + memset( weaponGot, -1, sizeof( weaponGot ) ); + + onCol = 0; + weaponsOnRow = 0; + rowsUsed = 0; + currentWeaponCol = -1; + + //Loop though every weapon in weaponRows (starting on row 2 - WHY TIM WHY!) + //TiM: ... because :) + for ( i = 0, onRow = 1; i < 15; i++ ) + { + if (onCol == 3) + { + onCol = 0; + weaponsOnRow = 0; + onRow++; + + if (onRow > 5) //Something has gone wrong! + break; + } + + if ( weaponRows[onRow][onCol] > 0) + { //Double check this is a weapon + if (( bits & ( 1 << weaponRows[onRow][onCol] ) ) && (weaponsOnRow == 0)) + { //If we have this weapon And it is the first weapon on this row we have + weaponGot[rowsUsed] = onRow; + weaponsOnRow++; + rowsUsed++; + } + + if ((cg.predictedPlayerState.weapon == weaponRows[onRow][onCol]) && (rowsUsed == num)) + { //If this is the selected weapon record what column it is on + currentWeaponCol = onCol; + } + } + + onCol++; + } + + //If they selected a row that doesn't exist + if (weaponGot[num - 1] == -1) + return; //(dont need to worry about num being zero because of tims hack ^^) + + do + { //Loop though this row until we come accross a weapon which the player has got and is not "null" (0) + currentWeaponCol++; + + if (currentWeaponCol == 3) + { + currentWeaponCol = 0; + } + } while ((weaponRows[ weaponGot[num - 1] ][currentWeaponCol] == 0) || !( bits & ( 1 << weaponRows[weaponGot[num - 1]][currentWeaponCol] ))); + + cg.weaponSelect = weaponRows[weaponGot[num - 1]][currentWeaponCol]; + + //TiM - based on the number we pressed, and whichever + //weapons we have in sequential order, select the one that corresponds. + + //Start at number 2, skipping null hand. He owns us all + /*for ( i = WP_TRICORDER, weaponCount = 0; i < MAX_WEAPONS; i++ ) { + //if we have that weapon + if ( bits & ( 1 << i ) ) { + weaponCount++; + + if ( weaponCount == num ) { + cg.weaponSelect = i; + return; + } + } + }*/ +} + +/* +=================== +CG_OutOfAmmoChange + +The current weapon has just run out of ammo +=================== +*/ +void CG_OutOfAmmoChange( qboolean altfire ) { + int i; + + cg.weaponSelectTime = cg.time; + + for ( i = 15 ; i > 0 ; i-- ) + { + if (altfire) + { + if ( CG_WeaponAltSelectable( i ) ) + { + cg.weaponSelect = i; + break; + } + } + else + { + if ( CG_WeaponSelectable( i ) ) + { + cg.weaponSelect = i; + break; + } + } + } +} + + + +/* +=================================================================================================== + +WEAPON EVENTS + +=================================================================================================== +*/ + +/* +================ +CG_FireWeapon + +Caused by an EV_FIRE_WEAPON event +================ +*/ +int tris_state = 0; +void CG_FireWeapon( centity_t *cent, qboolean alt_fire ) { + entityState_t *ent; + weaponInfo_t *weap; + int rpg_effectsgun; + int rpg_tripmines; + const char *info; + //const char *info2; + + ent = ¢->currentState; + if ( ent->weapon == WP_NONE || ent->weapon == WP_NULL_HAND ) { + return; + } + if ( ent->weapon >= WP_NUM_WEAPONS ) { + CG_Error( "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" ); + return; + } + weap = &cg_weapons[ ent->weapon ]; + + // mark the entity as muzzle flashing, so when it is added it will + // append the flash to the weapon model + cent->muzzleFlashTime = cg.time; + + // lightning gun only does this this on initial press + if ( ent->weapon == WP_PHASER /*|| + ent->weapon == WP_DERMAL_REGEN*/ + || ent->weapon == WP_TOOLKIT + || ent->weapon == WP_MEDKIT + || (!(cent->currentState.eFlags & EF_ALT_FIRING) && ent->weapon == WP_DISRUPTOR ) + || (cent->currentState.eFlags & EF_ALT_FIRING && ent->weapon == WP_COMPRESSION_RIFLE ) + ) + { + if ( cent->pe.lightningFiring ) { + return; + } + } + + // play quad sound if needed +/* if ( cent->currentState.powerups & ( 1 << PW_QUAD ) ) { + trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.media.quadSound ); + }*/ + + // play a sound + info = CG_ConfigString( CS_SERVERINFO ); + rpg_tripmines = atoi( Info_ValueForKey( info, "rpg_invisibletripmines" ) ); + rpg_effectsgun = atoi( Info_ValueForKey( info, "rpg_effectsgun" ) ); + if (alt_fire) + { + //RPG-X: RedTechie - Wrong place for show tris + /*if( ent->weapon == WP_TR116 ) + { + if(tris_state == 1) + tris_state = 0; + else + tris_state = 1; + + trap_Cvar_Set("r_showtris", va("%i",tris_state)); + }*/ + if ( weap->altFlashSnd ) + { + //TiM : Hark, I smell hackery again + //admin alt hypos no fire coz it grinds my teeth + if ( cgs.clientinfo[cg.snap->ps.clientNum].isAdmin/*cgs.clientinfo[cent->currentState.clientNum].pClass == PC_ADMIN*/ + && + cent->currentState.weapon == WP_VOYAGER_HYPO ) { + return; + } + + if(ent->weapon == WP_GRENADE_LAUNCHER){ + if(rpg_tripmines != 1){ + trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->altFlashSnd ); + } + }else{ + trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->altFlashSnd ); + } + } + } + else + { + if ( weap->flashSound ) + { + if(ent->weapon == WP_GRENADE_LAUNCHER){ + if((rpg_effectsgun == 1) || (rpg_tripmines == 1)){ + return; + }else{ + trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->flashSound ); + } + }else{ + trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->flashSound ); + } + } + } +} + +/* +================ +CG_FireSeeker + +Caused by an EV_FIRE_WEAPON event +================ +*/ +void CG_FireSeeker( centity_t *cent ) +{ + entityState_t *ent; + weaponInfo_t *weap; + + ent = ¢->currentState; + weap = &cg_weapons[ WP_COFFEE ]; + + trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->flashSound ); +} + +/* +================= +CG_MissileHitWall + +Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing +================= +*/ +void CG_MissileHitWall( centity_t *cent, int weapon, vec3_t origin, vec3_t dir ) +{ + qhandle_t mod; + qhandle_t mark; + qhandle_t shader; + sfxHandle_t sfx; + float radius; + float light; + vec3_t lightColor; + localEntity_t *le; + qboolean isSprite; + int duration; + qboolean alphaFade; +// weaponInfo_t *weaponInfo = &cg_weapons[weapon]; + + mark = 0; + radius = 32; + sfx = 0; + mod = 0; + shader = 0; + light = 0; + lightColor[0] = 1; + lightColor[1] = 1; + lightColor[2] = 0; + + // set defaults + isSprite = qfalse; + duration = 600; + + switch ( weapon ) { + default: + case WP_PHASER: + // no explosion at LG impact, it is added with the beam + mark = cgs.media.holeMarkShader; + radius = 12; + break; + case WP_DERMAL_REGEN: + // no explosion at LG impact, it is added with the beam + mark = cgs.media.holeMarkShader; + radius = 12; + break; + case WP_GRENADE_LAUNCHER: + FX_GrenadeExplode( origin, dir ); + return; + break; + case WP_DISRUPTOR: + FX_StasisWeaponHitWall( origin, dir, 2 ); //cent->currentState.time2 + return; + break; + case WP_NULL_HAND: + /*mod = cgs.media.ringFlashModel; + shader = cgs.media.imodExplosionShader; + mark = cgs.media.energyMarkShader; + radius = 24;*/ + break; + case WP_COMPRESSION_RIFLE: + //mod = cgs.media.ringFlashModel; + //shader = cgs.media.imodExplosionShader; + //mark = cgs.media.energyMarkShader; + //radius = 24; + FX_CompressionExplosion( cent->lerpOrigin, origin, dir, qfalse ); + return; + break; + case WP_TR116: + //FX_TetrionAltHitWall( origin, dir ); + return; + break; +/* case WP_COFFEE: + if (cent->currentState.eFlags & EF_ALT_FIRING) + { + FX_ScavengerAltExplode( origin, dir ); + } + else + { + FX_ScavengerWeaponHitWall( origin, dir, qfalse ); + } + return; + break;*/ +/* case WP_MEDKIT: + if ( !( cent->currentState.eFlags & EF_ALT_FIRING )) + { + FX_BorgWeaponHitWall( origin, dir ); + } + return; + break;*/ + + case WP_QUANTUM_BURST: + if ( cent->currentState.eFlags & EF_ALT_FIRING ) + { + FX_QuantumAltHitWall( origin, dir ); + } + else + { + FX_QuantumHitWall( origin, dir ); + } + return; + break; + } + + if ( sfx ) { + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, sfx ); + } + + // + // create the explosion + // + if ( mod ) { + le = CG_MakeExplosion( origin, dir, + mod, shader, + duration, 1, isSprite ); + le->light = light; + VectorCopy( lightColor, le->lightColor ); + } + + // + // impact mark + // + alphaFade = (mark == cgs.media.energyMarkShader); // plasma fades alpha, all others fade color + CG_ImpactMark( mark, origin, dir, random()*360, 1,1,1,1, alphaFade, radius, qfalse ); +} + + +/* +================= +CG_MissileHitPlayer +================= +*/ +void CG_MissileHitPlayer( centity_t *cent, int weapon, vec3_t origin, vec3_t dir) +{ + if (cent) + { // Showing blood is a no-no. + +// CG_Bleed( origin, cent->currentState.otherEntityNum ); + } + + CG_MissileHitWall( cent, weapon, origin, dir ); +} + + +/* +================= +CG_BounceEffect + +Caused by an EV_BOUNCE | EV_BOUNCE_HALF event +================= +*/ + +// big fixme. none of these sounds should be registered at runtime +void CG_BounceEffect( centity_t *cent, int weapon, vec3_t origin, vec3_t normal ) +{ + int rpg_tripmines; + const char *info; + + switch( weapon ) + { + case WP_GRENADE_LAUNCHER: + info = CG_ConfigString( CS_SERVERINFO ); + rpg_tripmines = atoi( Info_ValueForKey( info, "rpg_invisibletripmines" ) ); + if(rpg_tripmines != 1){ + if ( rand() & 1 ) { + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, trap_S_RegisterSound(SOUND_DIR "glauncher/bounce1.wav") ); + } else { + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, trap_S_RegisterSound(SOUND_DIR "glauncher/bounce2.wav") ); + } + } + break; + + case WP_TR116: + //trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, trap_S_RegisterSound ( va(SOUND_DIR "tetrion/ricochet%d.wav", irandom(1, 3)) ) ); + //FX_TetrionRicochet( origin, normal ); + break; + + default: + if ( rand() & 1 ) { + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, trap_S_RegisterSound(SOUND_DIR "glauncher/bounce1.wav") ); + } else { + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, trap_S_RegisterSound(SOUND_DIR "glauncher/bounce2.wav") ); + } + break; + } +} + + + + +/* +============================================================================ + +BULLETS + +============================================================================ +*/ + + + +/* +====================== +CG_CalcMuzzlePoint +====================== +*/ + +extern qboolean PM_PlayerCrouching ( int legsAnim ); + +qboolean CG_CalcMuzzlePoint( centity_t *cent, vec3_t muzzle, qboolean isDecoy ) { + vec3_t forward; + //centity_t *cent; + int anim; + + /*if ( entityNum == cg.snap->ps.clientNum && !isDecoy ) { + VectorCopy( cg.snap->ps.origin, muzzle ); + muzzle[2] += cg.snap->ps.viewheight; + AngleVectors( cg.snap->ps.viewangles, forward, NULL, NULL ); + VectorMA( muzzle, 14, forward, muzzle ); + return qtrue; + }*/ + + //cent = &cg_entities[entityNum]; + if ( !cent->currentValid ) { + return qfalse; + } + + //if ( !isDecoy ) + VectorCopy( cent->currentState.pos.trBase, muzzle ); + //else + // VectorCopy( cent->currentState.origin, muzzle ); + + AngleVectors( cent->currentState.apos.trBase, forward, NULL, NULL ); + anim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT; + if ( PM_PlayerCrouching( cent->currentState.legsAnim ) ) { + muzzle[2] += CROUCH_VIEWHEIGHT; + } else { + muzzle[2] += DEFAULT_VIEWHEIGHT; + } + + VectorMA( muzzle, 14, forward, muzzle ); + + return qtrue; + +} + + +/* +================ +CG_SurfaceExplosion + +Adds an explosion to a surface +================ +*/ + +#define NUM_SPARKS 12 +#define NUM_PUFFS 1 +#define NUM_EXPLOSIONS 4 + +void CG_SurfaceExplosion( vec3_t origin, vec3_t normal, float radius, float shake_speed, qboolean smoke ) +{ + localEntity_t *le; + vec3_t direction, new_org; + vec3_t sprayvel, velocity = { 0, 0, 0 }; + vec3_t temp_org, temp_vel; + float scale, dscale; + int i, numSparks; + + //Sparks + + numSparks = 32 + (random() * 16.0f); + + //VectorSet( normal, 0, 0, 1 ); + + for ( i = 0; i < numSparks; i++ ) + { + scale = 0.25f + (random() * 2.0f); + dscale = -scale*0.5; + + FXE_Spray( normal, 500, 150, 1.0f, sprayvel); + + FX_AddTrail( origin, + sprayvel, + qtrue, + 32.0f, + -64.0f, + scale, + -scale, + 1.0f, + 0.0f, + 0.25f, + 4000.0f, + cgs.media.sparkShader); + } + + //Smoke + + //Move this out a little from the impact surface + VectorMA( origin, 4, normal, new_org ); + VectorSet( velocity, 0.0f, 0.0f, 16.0f ); + + for ( i = 0; i < 4; i++ ) + { + VectorSet( temp_org, new_org[0] + (crandom() * 16.0f), new_org[1] + (crandom() * 16.0f), new_org[2] + (random() * 4.0f) ); + VectorSet( temp_vel, velocity[0] + (crandom() * 8.0f), velocity[1] + (crandom() * 8.0f), velocity[2] + (crandom() * 8.0f) ); + + FX_AddSprite( temp_org, + temp_vel, + qfalse, + radius /**96.0f*/ + (random() * 12.0f), + 16.0f, + 1.0f, + 0.0f, + 20.0f + (crandom() * 90.0f), + 0.5f, + 2000.0f, + cgs.media.smokeShader); + } + + //Core of the explosion + + //Orient the explosions to face the camera + VectorSubtract( cg.refdef.vieworg, origin, direction ); + VectorNormalize( direction ); + + //Tag the last one with a light + le = CG_MakeExplosion2( origin, direction, cgs.media.explosionModel, 5, cgs.media.surfaceExplosionShader, + 500, qfalse, radius * 0.02f + (random() * 0.3f), LEF_NONE); + le->light = 150; + VectorSet( le->lightColor, 0.9f, 0.8f, 0.5f ); + + for ( i = 0; i < NUM_EXPLOSIONS-1; i ++) + { + VectorSet( new_org, (origin[0] + (32 + (crandom() * 8))*crandom()), (origin[1] + (32 + (crandom() * 8))*crandom()), (origin[2] + (32 + (crandom() * 8))*crandom()) ); + le = CG_MakeExplosion2( new_org, direction, cgs.media.explosionModel, 5, cgs.media.surfaceExplosionShader, + 300 + (rand() & 99), qfalse, radius * 0.05f + (crandom() *0.3f), LEF_NONE); + } + + //Shake the camera + CG_ExplosionEffects( origin, shake_speed, 350 ); + +} + +void CG_PlayShooterSound(centity_t *cent) { + weaponInfo_t *weap; + + weap = &cg_weapons[cent->currentState.eventParm]; + + switch(cent->currentState.eventParm) { + case WP_COMPRESSION_RIFLE: + case WP_GRENADE_LAUNCHER: + case WP_QUANTUM_BURST: + trap_S_StartSound(cent->currentState.origin, cent->currentState.number, CHAN_VOICE, weap->flashSound); + break; + case WP_DISRUPTOR: + trap_S_StartSound(cent->currentState.origin, cent->currentState.number, CHAN_VOICE, weap->altFlashSnd); + break; + } +} diff --git a/cgame/cgame.def b/cgame/cgame.def new file mode 100644 index 0000000..2ee748e --- /dev/null +++ b/cgame/cgame.def @@ -0,0 +1,3 @@ +EXPORTS + vmMain + dllEntry diff --git a/cgame/cgame.dsp b/cgame/cgame.dsp new file mode 100644 index 0000000..4799da7 --- /dev/null +++ b/cgame/cgame.dsp @@ -0,0 +1,337 @@ +# Microsoft Developer Studio Project File - Name="cgame" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=cgame - 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 "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 "cgame.mak" CFG="cgame - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "cgame - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "cgame - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/StarTrek/Code-DM/cgame", VFJBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "cgame - 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 Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /G6 /W4 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FR /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o "NUL" /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE 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 /subsystem:windows /dll /machine:I386 +# ADD LINK32 /nologo /base:"0x30000000" /subsystem:windows /dll /map /machine:I386 /out:"../Release/cgamex86.dll" +# SUBTRACT LINK32 /incremental:yes /debug + +!ELSEIF "$(CFG)" == "cgame - 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 "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /G5 /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /YX /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE 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 /subsystem:windows /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 /nologo /base:"0x30000000" /subsystem:windows /dll /map /debug /machine:I386 /out:"../debug/cgamex86.dll" +# SUBTRACT LINK32 /profile /incremental:no /nodefaultlib + +!ENDIF + +# Begin Target + +# Name "cgame - Win32 Release" +# Name "cgame - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "c" +# Begin Source File + +SOURCE=..\game\bg_lib.c +# PROP Exclude_From_Build 1 +# End Source File +# Begin Source File + +SOURCE=..\game\bg_misc.c +# End Source File +# Begin Source File + +SOURCE=..\game\bg_oums.c +# End Source File +# Begin Source File + +SOURCE=..\game\bg_pmove.c +# End Source File +# Begin Source File + +SOURCE=..\game\bg_slidemove.c +# End Source File +# Begin Source File + +SOURCE=.\cg_consolecmds.c +# End Source File +# Begin Source File + +SOURCE=.\cg_draw.c +# End Source File +# Begin Source File + +SOURCE=.\cg_drawtools.c +# End Source File +# Begin Source File + +SOURCE=.\cg_effects.c +# End Source File +# Begin Source File + +SOURCE=.\cg_ents.c +# End Source File +# Begin Source File + +SOURCE=.\cg_env.c +# End Source File +# Begin Source File + +SOURCE=.\cg_event.c +# End Source File +# Begin Source File + +SOURCE=.\cg_info.c +# End Source File +# Begin Source File + +SOURCE=.\cg_localents.c +# End Source File +# Begin Source File + +SOURCE=.\cg_main.c +# End Source File +# Begin Source File + +SOURCE=.\cg_marks.c +# End Source File +# Begin Source File + +SOURCE=.\cg_players.c +# End Source File +# Begin Source File + +SOURCE=.\cg_playerstate.c +# End Source File +# Begin Source File + +SOURCE=.\cg_predict.c +# End Source File +# Begin Source File + +SOURCE=.\cg_scoreboard.c +# End Source File +# Begin Source File + +SOURCE=.\cg_screenfx.c +# End Source File +# Begin Source File + +SOURCE=.\cg_servercmds.c +# End Source File +# Begin Source File + +SOURCE=.\cg_snapshot.c +# End Source File +# Begin Source File + +SOURCE=.\cg_syscalls.c +# End Source File +# Begin Source File + +SOURCE=.\cg_view.c +# End Source File +# Begin Source File + +SOURCE=.\cg_weapons.c +# End Source File +# Begin Source File + +SOURCE=.\fx_borg.c +# End Source File +# Begin Source File + +SOURCE=.\fx_compression.c +# End Source File +# Begin Source File + +SOURCE=.\fx_dreadnought.c +# End Source File +# Begin Source File + +SOURCE=.\fx_grenade.c +# End Source File +# Begin Source File + +SOURCE=.\fx_imod.c +# End Source File +# Begin Source File + +SOURCE=.\fx_item.c +# End Source File +# Begin Source File + +SOURCE=.\fx_lib.c +# End Source File +# Begin Source File + +SOURCE=.\fx_misc.c +# End Source File +# Begin Source File + +SOURCE=.\fx_phaser.c +# End Source File +# Begin Source File + +SOURCE=.\fx_quantum.c +# End Source File +# Begin Source File + +SOURCE=.\fx_scavenger.c +# End Source File +# Begin Source File + +SOURCE=.\fx_stasis.c +# End Source File +# Begin Source File + +SOURCE=.\fx_tetrion.c +# End Source File +# Begin Source File + +SOURCE=.\fx_transporter.c +# End Source File +# Begin Source File + +SOURCE=..\game\q_math.c +# End Source File +# Begin Source File + +SOURCE=..\game\q_shared.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=..\game\bg_local.h +# End Source File +# Begin Source File + +SOURCE=..\game\bg_oums.h +# End Source File +# Begin Source File + +SOURCE=..\game\bg_public.h +# End Source File +# Begin Source File + +SOURCE=.\cg_anims.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=.\cg_screenfx.h +# End Source File +# Begin Source File + +SOURCE=.\cg_text.h +# End Source File +# Begin Source File + +SOURCE=.\cgame.def +# End Source File +# Begin Source File + +SOURCE=.\fx_local.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=.\tr_types.h +# End Source File +# End Group +# Begin Source File + +SOURCE=.\cg_syscalls.asm +# PROP Exclude_From_Build 1 +# End Source File +# Begin Source File + +SOURCE=.\cgame.bat +# PROP Exclude_From_Build 1 +# End Source File +# Begin Source File + +SOURCE=.\cgame.q3asm +# PROP Exclude_From_Build 1 +# End Source File +# End Target +# End Project diff --git a/cgame/cgame.dsw b/cgame/cgame.dsw new file mode 100644 index 0000000..dc9ecc5 --- /dev/null +++ b/cgame/cgame.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "cgame"=".\cgame.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/cgame/cgame.opt b/cgame/cgame.opt new file mode 100644 index 0000000..2f05cfa Binary files /dev/null and b/cgame/cgame.opt differ diff --git a/cgame/cgame.plg b/cgame/cgame.plg new file mode 100644 index 0000000..cd75540 --- /dev/null +++ b/cgame/cgame.plg @@ -0,0 +1,147 @@ + + +
+

Build Log

+

+--------------------Configuration: cgame - Win32 Debug-------------------- +

+

Command Lines

+Creating temporary file "C:\DOCUME~1\marcin\LOCALS~1\Temp\RSP194.tmp" with contents +[ +/nologo /G5 /MLd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR"Release/" /Fp"Release/cgame.pch" /YX /Fo"Release/" /Fd"Release/" /FD /c +"C:\stvoy\code-dm\game\bg_misc.c" +"C:\stvoy\code-dm\game\bg_pmove.c" +"C:\stvoy\code-dm\game\bg_slidemove.c" +"C:\stvoy\code-dm\cgame\cg_consolecmds.c" +"C:\stvoy\code-dm\cgame\cg_draw.c" +"C:\stvoy\code-dm\cgame\cg_drawtools.c" +"C:\stvoy\code-dm\cgame\cg_effects.c" +"C:\stvoy\code-dm\cgame\cg_ents.c" +"C:\stvoy\code-dm\cgame\cg_env.c" +"C:\stvoy\code-dm\cgame\cg_event.c" +"C:\stvoy\code-dm\cgame\cg_info.c" +"C:\stvoy\code-dm\cgame\cg_localents.c" +"C:\stvoy\code-dm\cgame\cg_main.c" +"C:\stvoy\code-dm\cgame\cg_marks.c" +"C:\stvoy\code-dm\cgame\cg_players.c" +"C:\stvoy\code-dm\cgame\cg_playerstate.c" +"C:\stvoy\code-dm\cgame\cg_predict.c" +"C:\stvoy\code-dm\cgame\cg_scoreboard.c" +"C:\stvoy\code-dm\cgame\cg_screenfx.c" +"C:\stvoy\code-dm\cgame\cg_servercmds.c" +"C:\stvoy\code-dm\cgame\cg_snapshot.c" +"C:\stvoy\code-dm\cgame\cg_syscalls.c" +"C:\stvoy\code-dm\cgame\cg_view.c" +"C:\stvoy\code-dm\cgame\cg_weapons.c" +"C:\stvoy\code-dm\cgame\fx_borg.c" +"C:\stvoy\code-dm\cgame\fx_compression.c" +"C:\stvoy\code-dm\cgame\fx_dreadnought.c" +"C:\stvoy\code-dm\cgame\fx_grenade.c" +"C:\stvoy\code-dm\cgame\fx_imod.c" +"C:\stvoy\code-dm\cgame\fx_item.c" +"C:\stvoy\code-dm\cgame\fx_lib.c" +"C:\stvoy\code-dm\cgame\fx_misc.c" +"C:\stvoy\code-dm\cgame\fx_phaser.c" +"C:\stvoy\code-dm\cgame\fx_quantum.c" +"C:\stvoy\code-dm\cgame\fx_scavenger.c" +"C:\stvoy\code-dm\cgame\fx_stasis.c" +"C:\stvoy\code-dm\cgame\fx_tetrion.c" +"C:\stvoy\code-dm\cgame\fx_transporter.c" +] +Creating command line "cl.exe @C:\DOCUME~1\marcin\LOCALS~1\Temp\RSP194.tmp" +Creating temporary file "C:\DOCUME~1\marcin\LOCALS~1\Temp\RSP195.tmp" with contents +[ +/nologo /base:"0x30000000" /subsystem:windows /dll /incremental:yes /pdb:"Release/cgamex86.pdb" /map:"Release/cgamex86.map" /debug /machine:I386 /def:".\cgame.def" /out:"../debug/cgamex86.dll" /implib:"Release/cgamex86.lib" +".\Release\bg_misc.obj" +".\Release\bg_oums.obj" +".\Release\bg_pmove.obj" +".\Release\bg_slidemove.obj" +".\Release\cg_consolecmds.obj" +".\Release\cg_draw.obj" +".\Release\cg_drawtools.obj" +".\Release\cg_effects.obj" +".\Release\cg_ents.obj" +".\Release\cg_env.obj" +".\Release\cg_event.obj" +".\Release\cg_info.obj" +".\Release\cg_localents.obj" +".\Release\cg_main.obj" +".\Release\cg_marks.obj" +".\Release\cg_players.obj" +".\Release\cg_playerstate.obj" +".\Release\cg_predict.obj" +".\Release\cg_scoreboard.obj" +".\Release\cg_screenfx.obj" +".\Release\cg_servercmds.obj" +".\Release\cg_snapshot.obj" +".\Release\cg_syscalls.obj" +".\Release\cg_view.obj" +".\Release\cg_weapons.obj" +".\Release\fx_borg.obj" +".\Release\fx_compression.obj" +".\Release\fx_dreadnought.obj" +".\Release\fx_grenade.obj" +".\Release\fx_imod.obj" +".\Release\fx_item.obj" +".\Release\fx_lib.obj" +".\Release\fx_misc.obj" +".\Release\fx_phaser.obj" +".\Release\fx_quantum.obj" +".\Release\fx_scavenger.obj" +".\Release\fx_stasis.obj" +".\Release\fx_tetrion.obj" +".\Release\fx_transporter.obj" +".\Release\q_math.obj" +".\Release\q_shared.obj" +] +Creating command line "link.exe @C:\DOCUME~1\marcin\LOCALS~1\Temp\RSP195.tmp" +

Output Window

+Compiling... +bg_misc.c +bg_pmove.c +bg_slidemove.c +cg_consolecmds.c +cg_draw.c +cg_drawtools.c +cg_effects.c +cg_ents.c +cg_env.c +cg_event.c +cg_info.c +cg_localents.c +cg_main.c +cg_marks.c +cg_players.c +cg_playerstate.c +cg_predict.c +cg_scoreboard.c +cg_screenfx.c +cg_servercmds.c +cg_snapshot.c +cg_syscalls.c +cg_view.c +cg_weapons.c +fx_borg.c +fx_compression.c +fx_dreadnought.c +fx_grenade.c +fx_imod.c +fx_item.c +fx_lib.c +fx_misc.c +fx_phaser.c +fx_quantum.c +fx_scavenger.c +fx_stasis.c +fx_tetrion.c +fx_transporter.c +Linking... + Creating library Release/cgamex86.lib and object Release/cgamex86.exp + + + +

Results

+cgamex86.dll - 0 error(s), 0 warning(s) +
+ + diff --git a/cgame/cgame.q3asm b/cgame/cgame.q3asm new file mode 100644 index 0000000..3d09044 --- /dev/null +++ b/cgame/cgame.q3asm @@ -0,0 +1,42 @@ +-o "C:\stvoy\code-DM\vm\cgame" +cg_main +..\cg_syscalls +cg_consolecmds +cg_draw +cg_drawtools +cg_effects +cg_ents +cg_env +cg_event +cg_info +cg_localents +cg_marks +cg_players +cg_playerstate +cg_predict +cg_scoreboard +cg_screenfx +cg_servercmds +cg_snapshot +cg_view +cg_weapons +bg_slidemove +bg_pmove +bg_lib +bg_misc +q_math +q_shared +fx_compression +fx_imod +fx_misc +fx_lib +fx_phaser +fx_scavenger +fx_tetrion +fx_transporter +fx_grenade +fx_quantum +fx_stasis +fx_item +fx_dreadnought +fx_borg \ No newline at end of file diff --git a/cgame/cgame.vcproj b/cgame/cgame.vcproj new file mode 100644 index 0000000..8ed940d --- /dev/null +++ b/cgame/cgame.vcproj @@ -0,0 +1,1354 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cgame/cgame.vcxproj b/cgame/cgame.vcxproj new file mode 100644 index 0000000..729ab23 --- /dev/null +++ b/cgame/cgame.vcxproj @@ -0,0 +1,531 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {EBB0D9E9-00FC-4DBA-AF4A-4052FE9B17B1} + cgame + + + + DynamicLibrary + false + + + DynamicLibrary + false + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + ..\Debug\ + ..\Debug\ + false + ..\Release\ + ..\Release\ + false + AllRules.ruleset + + + AllRules.ruleset + + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Release/cgame.tlb + + + + + MaxSpeed + true + Speed + true + lua\include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;XTRA;G_LUA;%(PreprocessorDefinitions) + MultiThreadedDebug + false + + + .\Release/cgame.pch + .\Release/ + .\Release/ + .\Release/ + true + Level4 + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + odbc32.lib;odbccp32.lib;lua52.lib;%(AdditionalDependencies) + C:\Program Files\Raven\Star Trek Voyager Elite Force\RPG-X2\cgamex86.dll + true + .\cgame.def + true + .\Release/cgamex86.pdb + true + .\Release/cgamex86.map + Windows + UseLinkTimeCodeGeneration + 0x30000000 + false + + + .\Release/cgamex86.lib + MachineX86 + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + .\Release/cgame.tlb + + + + + /analyze %(AdditionalOptions) + MaxSpeed + OnlyExplicitInline + WIN32;NDEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + true + MultiThreaded + 4Bytes + true + + + .\Release/cgame.pch + .\Release/ + .\Release/ + .\Release/ + + + Level4 + true + CompileAsC + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + ../Release/cgamex86.dll + true + .\cgame.def + .\Release/cgamex86.pdb + true + .\Release/cgamex86.map + Windows + 0x30000000 + false + + + .\Release/cgamex86.lib + MachineX86 + + + + + true + Disabled + WIN32;_DEBUG;_WINDOWS + true + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + Disabled + WIN32;_DEBUG;_WINDOWS + true + MaxSpeed + WIN32;NDEBUG;_WINDOWS + true + + + + + + + + + + + + + + + + + + + Document + true + true + + + Document + true + true + + + Document + true + true + + + + + + \ No newline at end of file diff --git a/cgame/cgame.vcxproj.filters b/cgame/cgame.vcxproj.filters new file mode 100644 index 0000000..a97e595 --- /dev/null +++ b/cgame/cgame.vcxproj.filters @@ -0,0 +1,186 @@ + + + + + {90a720d9-41f5-4abf-91d4-5c109cd2d702} + c + + + {76164a83-7a3a-4473-b6db-f151d30a82d7} + h + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Header Files + + + + + + + + \ No newline at end of file diff --git a/cgame/cgame.vcxproj.user b/cgame/cgame.vcxproj.user new file mode 100644 index 0000000..ace9a86 --- /dev/null +++ b/cgame/cgame.vcxproj.user @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/cgame/fx_borg.c b/cgame/fx_borg.c new file mode 100644 index 0000000..6329b7b --- /dev/null +++ b/cgame/fx_borg.c @@ -0,0 +1,214 @@ +#include "cg_local.h" +#include "fx_local.h" + + +#define BORG_SPIN 0.6f + +//------------------------------------------------------------------------------ +/*void FX_BorgProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + float len; + vec3_t dir, end; + + FX_AddSprite( cent->lerpOrigin, NULL, qfalse, + 8.0f + ( random() * 24.0f ), 0.0f, + 1.0f, 1.0f, + random() * 360, 0.0f, + 1, + cgs.media.borgFlareShader); + + // Energy glow + FX_AddSprite( cent->lerpOrigin, NULL, qfalse, + 18.0f + ( random() * 24.0f ), 0.0f, + 0.2f, 0.1f, + random() * 360, 0.0f, + 1, + cgs.media.borgFlareShader); + + VectorSet( dir, crandom(), crandom(), crandom() ); + VectorNormalize( dir ); + len = random() * 12.0f + 18.0f; + VectorMA( cent->lerpOrigin, len, dir, end ); + FX_AddElectricity( cent->lerpOrigin, end, 0.2f, 0.6f, 0.0f, 0.3f, 0.0f, 5, cgs.media.borgLightningShaders[2], 1.0f ); +}*/ + +/* +------------------------- +FX_BorgWeaponHitWall +------------------------- +*/ /* +void FX_BorgWeaponHitWall( vec3_t origin, vec3_t normal ) +{ + weaponInfo_t *weaponInfo = &cg_weapons[WP_MEDKIT]; + + // Expanding shock ring + FX_AddQuad( origin, normal, + 0.5f, 6.4f, + 0.8, 0.0, + random() * 360.0f, + 200, + cgs.media.borgLightningShaders[0] ); + + // Impact core + FX_AddQuad( origin, normal, + 16.0f + ( random() * 8.0f ), 3.2f, + 0.6f, 0.0f, + cg.time * BORG_SPIN, + 100, + cgs.media.borgLightningShaders[0] ); + + //Sound + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->mainHitSound); + + CG_ImpactMark( cgs.media.scavMarkShader, origin, normal, random()*360, 1,1,1,0.2, qfalse, random() + 5.5f, qfalse ); +} */ + +/* +------------------------- +FX_BorgTaser +------------------------- +*/ +/* +void FX_BorgTaser( vec3_t end, vec3_t start ) +{ + float len; + vec3_t dis; + + FX_AddSprite( end, NULL, qfalse, 9.0f, 0.0f, 1.0f, 0.0f, 30.0f, 0.0f, 250, cgs.media.borgLightningShaders[0] ); + FX_AddSprite( end, NULL, qfalse, 18.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 150, cgs.media.borgLightningShaders[0] ); + FX_AddSprite( start, NULL, qfalse, 12.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 100, cgs.media.borgLightningShaders[0] ); + FX_AddSprite( start, NULL, qfalse, 6.0f, 0.0f, 1.0f, 0.0f, 60.0f, 0.0f, 150, cgs.media.borgLightningShaders[0] ); + + VectorSubtract( start, end, dis ); + len = VectorNormalize( dis ); + + if ( len < 96 ) + len = 0.6f; + else if ( len < 256 ) + len = 0.4f; + else if ( len > 512 ) + len = 0.1f; + else + len = 0.2f; + + FX_AddLine( start, end, 1.0f, 5.0f, 0.0f, 1.0f, 0.0f, 150, cgs.media.borgLightningShaders[1] ); + FX_AddElectricity( start, end, 0.2f, 3.0f, 0.0f, 1.0f, 0.0f, 150, cgs.media.borgLightningShaders[2], len ); + FX_AddElectricity( start, end, 0.5f, 2.0f, 0.0f, 1.0f, 0.0f, 150, cgs.media.borgLightningShaders[3], len ); +}*/ + +//------------------------------------------------ + +// unused! +/*void FX_BorgEyeBeam( vec3_t start, vec3_t end, vec3_t normal, qboolean large ) +{ + float width, alpha; + vec3_t rgb = {1.0f,0.0f,0.0f}; + + width = 0.5f + ( crandom() * 0.1 ); + if ( large ) + width *= 3.5; + + alpha = 0.4f + ( random() * 0.25 ); + + FX_AddLine2( start, end, 1.0f, + width, 0.0f, width, 0.0f, + alpha, alpha, + rgb, rgb, + 1.0f, + cgs.media.whiteLaserShader ); + + FX_AddSprite( start, NULL, qfalse, + 1.0f + (random() * 2.0f), 0.0f, + 0.6f, 0.6f, + 0.0f, 0.0f, 1.0f, + cgs.media.borgEyeFlareShader ); + + FX_AddQuad( end, normal, + 2.0f + (crandom() * 1.0f), 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f, + cgs.media.borgEyeFlareShader ); +}*/ + +#define BORG_PARTICLE_RADIUS 32 +//------------------------------------------------------------------------------------ +void FX_BorgTeleportParticles( vec3_t origin, vec3_t dir ) +{ + int i; + vec3_t neworg, vel; + + for ( i = 0; i < 26; i++ ) + { + VectorSet( neworg, + origin[0] + ( crandom() * ( BORG_PARTICLE_RADIUS * 0.5 ) ), + origin[1] + ( crandom() * ( BORG_PARTICLE_RADIUS * 0.5 ) ), + origin[2] + ( crandom() * 4.0f ) ); + VectorScale( dir, 32 + ( random() * 96 ), vel ); + + FX_AddSprite( neworg, vel, qfalse, 1.0f + ( crandom() * 2.0f ), 0.0f, 1.0f, 0.0f, random() * 360, 0.0f, 1700, cgs.media.borgFlareShader ); + } +} + +//------------------------------------- +// unused +/* +void FX_BorgTeleport( vec3_t origin ) +{ + vec3_t org, org2, angles, dir; + localEntity_t *le; + + VectorSet( angles, 0, 0, 1 ); + VectorSet( org, origin[0], origin[1], origin[2] - 32 ); + FX_BorgTeleportParticles( origin, angles ); + + VectorSet( angles, 0, 0, -1 ); + VectorSet( org2, origin[0], origin[1], origin[2] + 32 ); + FX_BorgTeleportParticles( origin, angles ); + + VectorSubtract( org2, org, dir ); + VectorNormalize( dir ); + + le = FX_AddCylinder( org, dir, 96.0f, 0.0f, 1.0f, 48.0f, 1.0f, 48.0f, 1.0f, 0.0f, 1500, cgs.media.borgFlareShader, 0.5 ); + le->refEntity.data.cylinder.wrap = qtrue; + le->refEntity.data.cylinder.stscale = 24; + + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.borgBeamInSound ); +}*/ + +//------------------------------------- +//unused +/* +void FX_BorgTeleportTrails( vec3_t origin ) +{ + int i; + float scale; + vec3_t org, ang, angvect; + + for ( i = 0; i < 2; i++ ) + { + // position on the sphere + if ( i == 0 ) + { + ang[ROLL] = 0; + ang[PITCH] = sin( cg.time * 0.002f ) * 360; + ang[YAW] = cg.time * 0.04f; + } + else + { + ang[ROLL] = 0; + ang[PITCH] = sin( cg.time * 0.002f + 3.14159f ) * 360; + ang[YAW] = cg.time * 0.04f + 180.0f; + } + + AngleVectors( ang, angvect, NULL, NULL); + + // Set the particle position + org[0] = 12 * angvect[0] + origin[0]; + org[1] = 12 * angvect[1] + origin[1]; + org[2] = 32 * angvect[2] + origin[2]; + + scale = random() * 4.0f + 4.0f; + + FX_AddSprite( org, NULL, qtrue, scale, -scale, 1.0f, 1.0f, 0.0f, 0.0f, 200.0f + random() * 200.0f, cgs.media.borgFlareShader ); + } +}*/ diff --git a/cgame/fx_compression.c b/cgame/fx_compression.c new file mode 100644 index 0000000..bc346b7 --- /dev/null +++ b/cgame/fx_compression.c @@ -0,0 +1,434 @@ +//Compression rifle weapon effects + +#include "cg_local.h" +#include "fx_local.h" + +qboolean AltCompressionAftereffect(localEntity_t *le) +{ + localEntity_t *cyl = NULL; + qhandle_t shader = cgs.media.compressionAltBlastShader; + float percentLife = 1.0 - (le->endTime - cg.time)*le->lifeRate; + float alpha = 0.6 - (0.6*percentLife); + float length = 20; + vec3_t vec2, dir2; + + cyl = FX_AddCylinder( le->refEntity.origin, + le->data.spawner.dir, + length,// height, + 0,// dheight, + 10,//10+(30*(1-percentLife)),// scale, + 210,// dscale, + 10+(30*percentLife),// scale2, + 210,// dscale2, + alpha,// startalpha, + 0.0,// endalpha, + 500,// killTime, + shader, + 15);// bias ); + cyl->leFlags |= LEF_ONE_FRAME; + + VectorMA(le->refEntity.origin, length*2.0, le->data.spawner.dir, vec2); + VectorScale(le->data.spawner.dir, -1.0, dir2); + cyl = FX_AddCylinder( vec2, + dir2, + length,// height, + 0,// dheight, + 10,//10+(30*(1-percentLife)),// scale, + 210,// dscale, + 10+(30*percentLife),// scale2, + 210,// dscale2, + alpha,// startalpha, + 0.0,// endalpha, + 500,// killTime, + shader, + 15);// bias ); + cyl->leFlags |= LEF_ONE_FRAME; + + return qtrue; +} + +/* +------------------------- +FX_CompressionShot +------------------------- +*/ +#define MAXRANGE_CRIFLE 8192 +void FX_CompressionShot( vec3_t start, vec3_t dir ) +{ + localEntity_t *le; + vec3_t end; + trace_t trace; + qboolean render_impact = qtrue; + centity_t *traceEnt = NULL; + int clientNum = -1; + + VectorMA(start, MAXRANGE_CRIFLE, dir, end); + CG_Trace( &trace, start, NULL, NULL, end, 0, MASK_SHOT ); + + // draw the beam + le = FX_AddLine(start, trace.endpos, 1.0, 2.0, 0.0, 1.0, 1.0, 100.0, cgs.media.prifleBolt); + le->leFlags |= LEF_ONE_FRAME; + + // draw an impact at the endpoint of the trace + // If the beam hits a skybox, etc. it would look foolish to add in an explosion + if ( trace.surfaceFlags & SURF_NOIMPACT ) + { + render_impact = qfalse; + } + if ( render_impact ) + { + traceEnt = &cg_entities[trace.entityNum]; + clientNum = traceEnt->currentState.clientNum; + if ( (trace.entityNum != ENTITYNUM_WORLD) && (clientNum >= 0 || clientNum < MAX_CLIENTS) ) + { + FX_CompressionHit(trace.endpos); + } + else + { + FX_CompressionExplosion(start, trace.endpos, trace.plane.normal, qfalse); + } + } +} +/* +------------------------- +FX_CompressionShot +------------------------- +*/ +void FX_CompressionAltShot( vec3_t start, vec3_t dir ) +{ + vec3_t end, vel = {0,0,0}; + trace_t trace; + qboolean render_impact = qtrue; + centity_t *traceEnt = NULL; + int clientNum = -1; + + VectorMA(start, MAXRANGE_CRIFLE, dir, end); + CG_Trace( &trace, start, NULL, NULL, end, cg_entities[cg.predictedPlayerState.clientNum].currentState.number, MASK_SHOT ); + + // draw the beam + FX_AddLine( start, trace.endpos, 1.0f, 3.0f, 0.0f, 1.0f, 0.0f, 350/*125.0f*/, cgs.media.sparkShader ); + FX_AddLine( start, trace.endpos, 1.0f, 6.0f, 20.0f, 0.6f, 0.0f, 800/*175.0f*/, cgs.media.phaserShader);//compressionAltBeamShader ); + + FX_AddSpawner( start, dir, vel, NULL, qfalse, 0, + 0, 500, AltCompressionAftereffect, 10 ); + + // draw an impact at the endpoint of the trace + // If the beam hits a skybox, etc. it would look foolish to add in an explosion + if ( trace.surfaceFlags & SURF_NOIMPACT ) + { + render_impact = qfalse; + } + if ( render_impact ) + { + traceEnt = &cg_entities[trace.entityNum]; + clientNum = traceEnt->currentState.clientNum; + if ( (trace.entityNum != ENTITYNUM_WORLD) && (clientNum >= 0 || clientNum < MAX_CLIENTS) ) + { + FX_CompressionHit(trace.endpos); + } + else + { + FX_CompressionExplosion(start, trace.endpos, trace.plane.normal, qtrue); + } + } +} + +/* +------------------------- +FX_CompressionExplosion +------------------------- +*/ + +void FX_CompressionExplosion( vec3_t start, vec3_t origin, vec3_t normal, qboolean altfire ) +{ + localEntity_t *le; + vec3_t dir; + vec3_t velocity; //, shot_dir; + vec3_t hitpos; + float scale, dscale; + int i, j, numSparks; + weaponInfo_t *weaponInfo = &cg_weapons[WP_COMPRESSION_RIFLE]; + float distance; + + vec3_t color = {0.7, 0.43, 0.44}; + + int size = 2; + + //FX_CompressionHit( origin ); //TiM: let's test if the rifle doesn't make stuff explode when its shot :) + //return; + + //Sparks + //TiM: Calc spark count off proximity to effect + VectorSubtract ( cg.refdef.vieworg, origin, dir ); + distance = VectorNormalize( dir ); + distance = 50 * ( 1.0f - (distance / 128) ) ; + distance = Com_Clamp( 25, 50, distance ); + + numSparks = distance + (random() * 4.0f); //4 + + if (altfire) + { + numSparks *= 1.5f; + } + for ( i = 0; i < numSparks; i++ ) + { + scale = 10.0f + (random() * 1.0f); //.25 + dscale = -scale; + + //Randomize the direction + for (j = 0; j < 3; j ++ ) + { + //if ( j !=5 ) + //dir[j] = normal[j] + (0.75 * crandom()); + //else + dir[j] = normal[j] + (-1 * crandom()); //0.75 + } + + VectorNormalize(dir); + + //set the speed + VectorScale( dir, 200 + (50 * crandom()), velocity); //200 + + le = FX_AddTrail( origin, + velocity, + qtrue, //qtrue + 12.0f,//4 + -12.0f,//4 + scale, + -scale, + 1.0f, + 1.0f, + 0.5f, + 1000.0f, //1000 + cgs.media.orangeStarShader); + +// FXE_Spray( normal, 200, 50, 0.4f, le); + } + + VectorMA( origin, 8, normal, dir ); + VectorSet(velocity, 0, 0, 8); +/* + FX_AddSprite( dir, + velocity, + qfalse, + (altfire?50.0f:32.0f), + 16.0f, + 1.0f, + 0.0f, + random()*45.0f, + 0.0f, + (altfire?1300.0f:1000.0f), + cgs.media.steamShader ); +*/ + //Orient the explosions to face the camera + VectorSubtract( cg.refdef.vieworg, origin, dir ); + VectorNormalize( dir ); + + if (!altfire) + { + CG_InitLensFlare( origin, + 350, 350, + color, 1.2, 2.0, 1600, 200, + color, 1600, 200, 800, 20, qtrue, + 0, 0, qfalse, qtrue, + qfalse, 1.0, cg.time, 0, 0, 210); + + + VectorMA(origin, size, normal, hitpos); + + FX_AddSprite( hitpos, NULL, qfalse, size * size * 15.0f, -150.0f, + 1.0f, 0.0f, 360*random(), 0, 400, cgs.media.liteRedParticleShader ); + + FX_AddSprite( hitpos, NULL, qfalse, size * size * 25.0f, -150.0f, + 1.0f, 0.0f, 0.0f, 0, 400, cgs.media.liteRedParticleStreakShader ); + + + le = CG_MakeExplosion2( origin, dir, cgs.media.explosionModel, 5, cgs.media.electricalExplosionSlowShader, + 475, qfalse, 1.2f + ( crandom() * 0.3f), LEF_NONE); + le->light = 150; + le->refEntity.renderfx |= RF_NOSHADOW; + VectorSet( le->lightColor, 0.8f, 0.8f, 1.0f ); + + CG_ImpactMark( cgs.media.compressionMarkShader, origin, normal, random()*360, 1,1,1,1, qfalse, 12, qfalse ); + + //Shake the camera + CG_ExplosionEffects( origin, 1, 200 ); + } + else + { + le = CG_MakeExplosion2( origin, dir, cgs.media.explosionModel, 5, cgs.media.electricalExplosionSlowShader, + 500, qfalse, 2.2f + ( crandom() * 0.4f), LEF_NONE); + le->light = 200; + le->refEntity.renderfx |= RF_NOSHADOW; + VectorSet( le->lightColor, 0.8f, 0.8f, 1.0f ); + + CG_ImpactMark( cgs.media.compressionMarkShader, origin, normal, random()*360, 1,1,1,1, qfalse, 28, qfalse ); + + //Shake the camera + CG_ExplosionEffects( origin, 2, 240 ); + } + + // nice explosion sound at the point of impact + trap_S_StartSound(origin, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->mainHitSound); +} + +/* +------------------------- +FX_CompressionHit +------------------------- +*/ + +void FX_CompressionHit( vec3_t origin ) +{ + FX_AddSprite( origin, + NULL, + qfalse, + 32.0f, + -32.0f, + 1.0f, + 1.0f, + random()*360, + 0.0f, + 250.0f, + cgs.media.prifleImpactShader ); + + //FIXME: Play an impact sound with a body +// trap_S_StartSound (origin, NULL, 0, cgi_S_RegisterSound ("sound/weapons/prifle/fire.wav") ); +} + +void FX_PrifleBeamFire( vec3_t startpos, vec3_t endpos, vec3_t normal, qboolean spark, qboolean impact, qboolean empty ) +{ + refEntity_t beam; + sfxHandle_t sfx; + float size; + vec3_t velocity; + int sparks; + vec3_t rgb = { 1,0.9,0.6}, rgb2={1,0.3,0}; + + //vec3_t rgb3 = { 1.0, 1.0, 1.0 }; + + sfx = 0; + + // Draw beam first. + memset( &beam, 0, sizeof( beam ) ); + + VectorCopy( startpos, beam.origin); + VectorCopy( endpos, beam.oldorigin ); + beam.reType = RT_LINE; + if (empty) + { + beam.customShader = cgs.media.phaserEmptyShader; + } + else + { + beam.customShader = cgs.media.prifleBeam; + } + AxisClear( beam.axis ); + beam.shaderRGBA[0] = 0xff; + beam.shaderRGBA[1] = 0xff; + beam.shaderRGBA[2] = 0xff; + beam.shaderRGBA[3] = 0xff; + if (empty) + { + beam.data.line.width = 1.0f + ( crandom() * 0.6f ); + } + else + { + beam.data.line.width = 2.5f + ( crandom() * 0.6f ); + } + beam.data.line.stscale = 5.0; + trap_R_AddRefEntityToScene( &beam ); + + // Now draw the hit graphic + + // no explosion at LG impact, it is added with the beam + + if ( sfx ) + { + Com_Printf("playing %s\n", "phaser sound"); + trap_S_StartSound( endpos, ENTITYNUM_WORLD, CHAN_AUTO, sfx ); + } + + // + // impact mark + // + if (impact) + { + if (!empty) + { // normal. + CG_ImpactMark( cgs.media.scavMarkShader, endpos, normal, random()*360, 1,1,1,0.2, qfalse, + random() + 1, qfalse ); + + //VectorCopy( endpos, phaserFlare.worldCoord ); + + /*CG_InitLensFlare( endpos, + 80, + 80, + rgb, + 1.2, + 1.5, + 1600, + 200, + colorTable[CT_BLACK], + 1600, + 200, + 80, + 5, + qfalse, + 5, + 40, + qfalse, + qfalse, + qfalse, + 1.0, + 1.0, + 200.0, + 200.0, + 200.0 );*/ + + //CG_InitLensFlare( endpos, + // 30, 30, + // rgb, 1.2, 2.0, 1600, 200, + // colorTable[CT_BLACK], 1600, 200, 410, 15, qfalse, + // 0, 0, qfalse, qtrue, + // qfalse, 1.0, cg.time, 0, 0, 50); + + //TiM : Add your basic cheesy 'seen-way-too-much-in-movies-these-days' anamorphic lens streak :) + //CG_DrawLensFlare( &phaserFlare ); + //FX_AddSprite( endpos, NULL, qfalse, random() * 1.25 + 5.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 50.0, cgs.media.blueParticleStreakShader ); //1.5f + + //FX_AddQuad2( endpos, normal, random() * 1.25 + 8.0f, 0.0f, 1.0f, 1.0f, rgb3, rgb3, 270, 50.0, cgs.media.blueParticleStreakShader ); + //eh... looked bad :P + + FX_AddQuad2( endpos, normal, random() * 1.25 + 1.5f, 0.0f, 1.0f, 0.0f, rgb, rgb2, rand() % 360, 500 + random() * 200, + cgs.media.sunnyFlareShader ); + } + else + { // Wuss hit when empty. + FX_AddQuad2( endpos, normal, random() * .75 + 1.0f, 0.0f, 0.5f, 0.0f, rgb, rgb2, rand() % 360, 300 + random() * 200, + cgs.media.sunnyFlareShader ); + } + } + + // "Fun" sparks... Not when empty. + if ( spark && !empty) + { + sparks = rand() & 1 + 1; + for(;sparks>0;sparks--) + { + size = 0.2f + (random() * 0.4); + FXE_Spray( normal, 200, 75, 0.8f, velocity); + if (rand() & LEF_USE_COLLISION) + { // This spark bounces. + FX_AddTrail( endpos, velocity, qtrue, 5.0f, -15.0f, + size, -size, 1.0f, 0.5f, 0.4f, 500.0f, cgs.media.sparkShader); + } + else + { + FX_AddTrail( endpos, velocity, qtrue, 5.0f, -15.0f, + size, -size, 1.0f, 0.5f, 0.0, 500.0f, cgs.media.sparkShader); + } + } + } +} + diff --git a/cgame/fx_dreadnought.c b/cgame/fx_dreadnought.c new file mode 100644 index 0000000..105a19c --- /dev/null +++ b/cgame/fx_dreadnought.c @@ -0,0 +1,310 @@ +#include "cg_local.h" +#include "fx_local.h" + +/* +------------------------- +FX_DreadnoughtHitWall +------------------------- +*/ +/*void FX_DreadnoughtHitWall( vec3_t origin, vec3_t normal, qboolean spark ) +{ + float scale = 1.0f + ( random() * 1.0f ); + int num, i; + localEntity_t *le = NULL; + vec3_t vel; +// weaponInfo_t *weaponInfo = &cg_weapons[WP_DERMAL_REGEN]; + +// trap_S_StartSound(origin, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->altHitSound); + + le = FX_AddQuad( origin, normal, 32.0 + random() * 48, 0, 0.5, 0.5, 0, 100, cgs.media.purpleParticleShader ); + if (le) + { + le->leFlags |= LEF_ONE_FRAME; + } + le = FX_AddQuad( origin, normal, 24.0 + random() * 32, 0, 0.6, 0.6, 0, 100, cgs.media.ltblueParticleShader ); + if (le) + { + le->leFlags |= LEF_ONE_FRAME; + } + + CG_ImpactMark( cgs.media.scavMarkShader, origin, normal, random()*360, 1,1,1,0.2, qfalse, + random() * 4 + 8, qfalse ); + + if ( spark ) + { + trap_R_AddLightToScene( origin, 75 + (rand()&31), 1.0, 0.8, 1.0 ); + + // Drop some sparks + num = (int)(random() * 2) + 2; + + for ( i = 0; i < num; i++ ) + { + scale = 0.6f + random(); + if ( rand() & 1 ) + FXE_Spray( normal, 70, 80, 0.9f, vel); + else + FXE_Spray( normal, 80, 200, 0.5f, vel); + + FX_AddTrail( origin, vel, qfalse, 8.0f + random() * 8, -48.0f, + scale, -scale, 1.0f, 0.8f, 0.4f, 600.0f, cgs.media.spark2Shader ); + } + } +}*/ + +/* +------------------------- +FX_DreadnoughtFire +------------------------- +*/ +/*void FX_DreadnoughtFire( vec3_t origin, vec3_t end, vec3_t normal, qboolean spark, qboolean impact ) +{ +// localEntity_t *le = NULL; + float scale = 1.0f + ( random() * 1.0f ); + refEntity_t beam; + + // Draw beams first. + memset( &beam, 0, sizeof( beam ) ); + VectorCopy( origin, beam.origin); + VectorCopy( end, beam.oldorigin ); + beam.reType = RT_LINE; + beam.customShader = cgs.media.dnBoltShader; + AxisClear( beam.axis ); + beam.shaderRGBA[0] = 0xff*0.2; + beam.shaderRGBA[1] = 0xff*0.2; + beam.shaderRGBA[2] = 0xff*0.2; + beam.shaderRGBA[3] = 0xff; + beam.data.line.stscale = 2.0; + beam.data.line.width = scale*6; + trap_R_AddRefEntityToScene( &beam ); + + // Add second core beam + memset( &beam, 0, sizeof( beam ) ); + VectorCopy( origin, beam.origin); + VectorCopy( end, beam.oldorigin ); + beam.reType = RT_LINE; + beam.customShader = cgs.media.dnBoltShader; + AxisClear( beam.axis ); + beam.shaderRGBA[0] = 0xff*0.8; + beam.shaderRGBA[1] = 0xff*0.8; + beam.shaderRGBA[2] = 0xff*0.8; + beam.shaderRGBA[3] = 0xff; + beam.data.line.stscale = 1.0; + beam.data.line.width = scale*4.5; + trap_R_AddRefEntityToScene( &beam ); + + if (spark) + { + // Add first electrical bolt + memset( &beam, 0, sizeof( beam ) ); + VectorCopy( origin, beam.origin); + VectorCopy( end, beam.oldorigin ); + beam.reType = RT_ELECTRICITY; + beam.customShader = cgs.media.dnBoltShader; + AxisClear( beam.axis ); + beam.shaderRGBA[0] = 0xff*0.8; + beam.shaderRGBA[1] = 0xff*0.8; + beam.shaderRGBA[2] = 0xff*0.8; + beam.shaderRGBA[3] = 0xff; + beam.data.electricity.stscale = 1.0; + beam.data.electricity.width = scale*0.5; + beam.data.electricity.deviation = 0.2; + trap_R_AddRefEntityToScene( &beam ); + } + + // Add next electrical bolt + memset( &beam, 0, sizeof( beam ) ); + VectorCopy( origin, beam.origin); + VectorCopy( end, beam.oldorigin ); + beam.reType = RT_ELECTRICITY; + beam.customShader = cgs.media.dnBoltShader; + AxisClear( beam.axis ); + beam.shaderRGBA[0] = 0xff*0.8; + beam.shaderRGBA[1] = 0xff*0.8; + beam.shaderRGBA[2] = 0xff*0.8; + beam.shaderRGBA[3] = 0xff; + beam.data.electricity.stscale = 1.0; + beam.data.electricity.width = scale*0.75; + beam.data.electricity.deviation = 0.12; + trap_R_AddRefEntityToScene( &beam ); + +/* + + le = FX_AddLine( origin, end, 2.0f, scale * 6, 0.0f, 0.2f, 0.2f, 100, cgs.media.dnBoltShader ); + if (le) + { + le->leFlags |= LEF_ONE_FRAME; + } + + le = FX_AddLine( origin, end, 1.0f, scale * 4.5, 0.0f, 0.8f, 0.8f, 100, cgs.media.dnBoltShader ); + if (le) + { + le->leFlags |= LEF_ONE_FRAME; + } + + if ( spark ) + { + le = FX_AddElectricity( origin, end, 1.0f, scale * 0.5, 0, 0.8, 0.8, 100, cgs.media.dnBoltShader, 0.2 ); + if (le) + { + le->leFlags |= LEF_ONE_FRAME; + } + } + le = FX_AddElectricity( origin, end, 1.0f, scale * 0.75, 0, 0.8, 0.8, 100, cgs.media.dnBoltShader, 0.12 ); + if (le) + { + le->leFlags |= LEF_ONE_FRAME; + } + + // Add a subtle screen shake + CG_ExplosionEffects( origin, 1.0f, 15 ); + + if (impact) + { + FX_DreadnoughtHitWall( end, normal, spark ); + } +}*/ + +/* +------------------------- +FX_DreadnoughtProjectileThink + +Freaky random lightning burst +------------------------- +*/ +/*#define FX_DN_ALT_THINK_TIME 100 + +void FX_DreadnoughtProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ +// refEntity_t beam; + float scale; + + scale = flrandom(10.0, 15.0); + + // If this is a new thinking time, draw some starting stuff... + if (cent->miscTime < cg.time) + { + trace_t trace; + vec3_t fwd, right, boltdir, boltend, mins={-2,-2,-2}, maxs={2,2,2}; + float len; + localEntity_t *le; + int playSound = 1;//(irandom(0,1) == 0)?1:0; + + cent->miscTime = cg.time + FX_DN_ALT_THINK_TIME; + + VectorSubtract(cent->currentState.origin, cent->currentState.origin2, fwd); + + // Throw a sprite from the start to the end over the next + VectorScale(fwd, 1000.0*(1.0/FX_DN_ALT_THINK_TIME), boltdir); + le = FX_AddSprite(cent->currentState.origin2, boltdir, qfalse, scale*8, -scale*2, 1.0, 1.0, 0, 0, FX_DN_ALT_THINK_TIME, cgs.media.blueParticleShader); + le->light = 200; + le->refEntity.renderfx |= RF_NOSHADOW; + VectorSet( le->lightColor, 0.5f, 0.8f, 1.0f ); + + len = VectorNormalize(fwd); + + // Illegal if org and org2 are the same. + if (len<=0) + return; + + // Draw a bolt from the old position to the new. + FX_AddLine( cent->currentState.origin2, cent->currentState.origin, 1.0, scale*4, -scale*4, 0.75, 0.0, FX_DN_ALT_THINK_TIME*2, cgs.media.dnBoltShader); + + // ALSO draw an electricity bolt from the old position to the new. + FX_AddElectricity( cent->currentState.origin2, cent->currentState.origin, 0.2f, scale, -scale, 1.0, 0.5, FX_DN_ALT_THINK_TIME*2, cgs.media.dnBoltShader, 0.5 ); + + // And a bright new sprite at the current locale. + FX_AddSprite(cent->currentState.origin, NULL, qfalse, scale*2, scale*4, 1.0, 1.0, 0, 0, FX_DN_ALT_THINK_TIME, cgs.media.blueParticleShader); + + // Put a sprite in the old position, fading away. + FX_AddSprite(cent->currentState.origin2, NULL, qfalse, scale*5, -scale*5, 1.0, 1.0, 0, 0, FX_DN_ALT_THINK_TIME*2, cgs.media.blueParticleShader); + + // Shoot rays out (roughly) to the sides to connect with walls or whatever... + // PerpendicularVector(right, fwd); + right[0] = fwd[1]; + right[1] = -fwd[0]; + right[2] = -fwd[2]; + + // Right vector + // The boltdir uses a random offset to the perp vector. + boltdir[0] = right[0] + flrandom(-0.25, 0.25); + boltdir[1] = right[1] + flrandom(-0.25, 0.25); + boltdir[2] = right[2] + flrandom(-1.0, 1.0); + + // Shoot a vector off to the side and trace till we hit a wall. + VectorMA(cent->currentState.origin, 256, boltdir, boltend); + CG_Trace( &trace, cent->currentState.origin, mins, maxs, boltend, cent->currentState.number, MASK_SOLID ); + + if (trace.fraction < 1.0) + { + VectorCopy(trace.endpos, boltend); + FX_AddElectricity( cent->currentState.origin, boltend, 0.2f, scale, -scale, 1.0, 0.5, FX_DN_ALT_THINK_TIME*2, cgs.media.dnBoltShader, 0.5 ); + // Put a sprite at the endpoint that stays. + FX_AddQuad(trace.endpos, trace.plane.normal, scale, -scale*0.5, 1.0, 0.5, 0.0, FX_DN_ALT_THINK_TIME*2, cgs.media.blueParticleShader); + if (playSound) + { + if (irandom(0,1)) + { + weaponInfo_t *weaponInfo = &cg_weapons[WP_DERMAL_REGEN]; + trap_S_StartSound(trace.endpos, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->alt_missileSound); + playSound = 0; + } + } + } + + // Left vector + // The boltdir uses a random offset to the perp vector. + boltdir[0] = -right[0] + flrandom(-0.25, 0.25); + boltdir[1] = -right[1] + flrandom(-0.25, 0.25); + boltdir[2] = -right[2] + flrandom(-1.0, 1.0); + + // Shoot a vector off to the side and trace till we hit a wall. + VectorMA(cent->currentState.origin, 256, boltdir, boltend); + CG_Trace( &trace, cent->currentState.origin, mins, maxs, boltend, cent->currentState.number, MASK_SOLID ); + + if (trace.fraction < 1.0) + { + VectorCopy(trace.endpos, boltend); + FX_AddElectricity( cent->currentState.origin, boltend, 0.2f, scale, -scale, 1.0, 0.5, FX_DN_ALT_THINK_TIME*2, cgs.media.dnBoltShader, 0.5 ); + // Put a sprite at the endpoint that stays. + FX_AddQuad(trace.endpos, trace.plane.normal, scale, -scale*0.5, 1.0, 0.5, 0.0, FX_DN_ALT_THINK_TIME*2, cgs.media.blueParticleShader); + if (playSound) + { + weaponInfo_t *weaponInfo = &cg_weapons[WP_DERMAL_REGEN]; + trap_S_StartSound(trace.endpos, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->alt_missileSound); + playSound = 0; + } + } + } +}*/ + +/* +------------------------- +FX_DreadnoughtShotMiss + +Alt-fire, miss effect +------------------------- +*/ +/*void FX_DreadnoughtShotMiss( vec3_t end, vec3_t dir ) +{ + vec3_t org; + weaponInfo_t *weaponInfo = &cg_weapons[WP_DERMAL_REGEN]; + + trap_S_StartSound(end, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->altHitSound); + + // Move me away from the wall a bit so that I don't z-buffer into it + VectorMA( end, 0.5, dir, org ); + + // Expanding rings +// FX_AddQuad( org, dir, 1, 24, 0.8, 0.2, random() * 360, 400, cgs.media.stasisRingShader ); +// FX_AddQuad( org, dir, 1, 60, 0.8, 0.2, random() * 360, 300, cgs.media.stasisRingShader ); + // Impact effect + FX_AddQuad( org, dir, 7, 35, 1.0, 0.0, random() * 360, 500, cgs.media.blueParticleShader ); + FX_AddQuad( org, dir, 5, 25, 1.0, 0.0, random() * 360, 420, cgs.media.ltblueParticleShader ); + + CG_ImpactMark( cgs.media.scavMarkShader, org, dir, random()*360, 1,1,1,0.6, qfalse, + 8 + random() * 2, qfalse ); + + FX_AddSprite( end, NULL, qfalse, flrandom(40,60), -50, 1.0, 0.0, random() * 360, 0, 500, cgs.media.blueParticleShader ); +}*/ + + diff --git a/cgame/fx_grenade.c b/cgame/fx_grenade.c new file mode 100644 index 0000000..fdf0237 --- /dev/null +++ b/cgame/fx_grenade.c @@ -0,0 +1,350 @@ +#include "cg_local.h" +#include "fx_local.h" + + +/* +------------------------- +FX_GrenadeThink +------------------------- +*/ + +void FX_GrenadeThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + FX_AddSprite( cent->lerpOrigin, NULL, qfalse, 8.0f + random() * 32.0f, 0.0f, 0.75f, 0.75f, 0, 0.0f, 1, cgs.media.dkorangeParticleShader ); + if ( rand() & 1 ) + FX_AddSprite( cent->lerpOrigin, NULL, qfalse, 16.0f + random() * 32.0f, 0.0f, 0.6f, 0.6f, 0, 0.0f, 1, cgs.media.yellowParticleShader ); +} + +/* +------------------------- +FX_GrenadeHitWall +------------------------- +*/ + +void FX_GrenadeHitWall( vec3_t origin, vec3_t normal ) +{ + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.grenadeExplodeSound ); + CG_SurfaceExplosion( origin, normal, 8, 1, qfalse ); +} + +/* +------------------------- +FX_GrenadeHitPlayer +------------------------- +*/ + +void FX_GrenadeHitPlayer( vec3_t origin, vec3_t normal ) +{ + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.grenadeExplodeSound ); + CG_SurfaceExplosion( origin, normal, 8, 1, qfalse ); +} + +/* +------------------------- +FX_GrenadeExplode +------------------------- +*/ + +void FX_GrenadeExplode( vec3_t origin, vec3_t normal ) +{ + localEntity_t *le; + qhandle_t null; + vec3_t direction, org, vel; + int i; + + VectorSet( direction, 0,0,1 ); + + // Add an explosion and tag a light to it + le = CG_MakeExplosion2( origin, direction, cgs.media.nukeModel, 5, null, 250, qfalse, 25.0f, LEF_FADE_RGB); + le->light = 150; + le->refEntity.renderfx |= RF_NOSHADOW; + + VectorSet( le->lightColor, 1.0f, 0.6f, 0.2f ); + + // Ground ring + FX_AddQuad( origin, normal, 5, 100, 1.0, 0.0, random() * 360, 300, cgs.media.bigShockShader ); + // Flare + VectorMA( origin, 12, direction, org ); + FX_AddSprite( org, NULL, qfalse, 160.0, -160.0, 1.0, 0.0, 0.0, 0.0, 200, cgs.media.sunnyFlareShader );//, FXF_NON_LINEAR_FADE ); + + for (i = 0; i < 12; i++) + { + float width, length; + FXE_Spray( normal, 470, 325, 0.5f, vel); + length = 24.0 + random() * 12; + width = 0.5 + random() * 2; + FX_AddTrail( origin, vel, qtrue, length, -length, width, -width, + 1.0f, 1.0f, 0.5f, 1000.0f, cgs.media.orangeTrailShader); + } + + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.grenadeExplodeSound ); + + // Smoke and impact +// FX_AddSpawner( origin, normal, NULL, NULL, 100, 25.0f, 2000.0f, (void *) CG_SmokeSpawn, NULL, 1024 ); + CG_ImpactMark( cgs.media.compressionMarkShader, origin, normal, random()*360, 1,1,1,1.0, qfalse, + random() * 16 + 48, qfalse ); +} + + +/*void FX_GrenadeShrapnelExplode( vec3_t origin, vec3_t norm ) +{ + localEntity_t *le; + vec3_t direction, org, vel; + int i; + + VectorCopy( norm, direction); + + // Add an explosion and tag a light to it + le = CG_MakeExplosion2( origin, direction, cgs.media.nukeModel, 5, (qhandle_t)NULL, 250, qfalse, 25.0f, LEF_FADE_RGB); + le->light = 150; + le->refEntity.renderfx |= RF_NOSHADOW; + + VectorSet( le->lightColor, 1.0f, 0.6f, 0.2f ); + + // Ground ring + FX_AddQuad( origin, norm, 5, 100, 1.0, 0.0, random() * 360, 300, cgs.media.bigShockShader ); + // Flare + VectorMA( origin, 12, direction, org ); + FX_AddSprite( org, NULL, qfalse, 160.0, -160.0, 1.0, 0.0, 0.0, 0.0, 200, cgs.media.sunnyFlareShader );//, FXF_NON_LINEAR_FADE ); + + for (i = 0; i < 12; i++) + { + float width, length; + FXE_Spray( norm, 470, 325, 0.5f, vel); + length = 24.0 + random() * 12; + width = 0.5 + random() * 2; + FX_AddTrail( origin, vel, qtrue, length, -length, width, -width, + 1.0f, 1.0f, 0.5f, 1000.0f, cgs.media.orangeTrailShader); + } + + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.grenadeExnull + // Smoke and impact + CG_ImpactMark( cgs.media.compressionMarkShader, origin, norm, random()*360, 1,1,1,1.0, qfalse, + random() * 16 + 48, qfalse ); +}*/ + +//----------------------------------- +//By: RedTechie - Imported/Modifyed from SP +//----------------------------------- +void FX_GrenadeShrapnelExplode( vec3_t origin, vec3_t norm ) +{ + localEntity_t *le; + //FXTrail *fx; + vec3_t direction, org, vel; + int i; + + CG_InitLensFlare( origin, + 350, 350, + colorTable[CT_DKRED1], 1.2, 2.0, 1600, 200, + colorTable[CT_DKRED1], 1600, 200, 800, 20, qtrue, + 0, 0, qfalse, qtrue, + qfalse, 1.0, cg.time, 90, 0, 300); + + //Orient the explosions to face the camera + VectorSubtract( cg.refdef.vieworg, origin, direction ); + VectorNormalize( direction ); + + VectorMA( origin, 12, direction, org ); + // Add an explosion and tag a light to it + le = CG_MakeExplosion2( org, direction, cgs.media.explosionModel, 6, cgs.media.surfaceExplosionShader, 700, qfalse, 1.2f + (random()*0.5f),LEF_FADE_RGB ); //RPG-X: RedTechie - Scale use to be 1.2f + (random()*0.3f) + le->light = 150; + le->refEntity.renderfx |= RF_NOSHADOW; + VectorSet( le->lightColor, 1.0f, 0.6f, 0.6f ); + + VectorMA( org, 8, norm, direction ); + VectorSet(vel, 0, 0, 8); + //Some smoke + FX_AddSprite( direction, + vel, + qfalse, + 20.0f + random()*50.0f,//1.2f + (random()*0.5f),//60.0f - random()*60.0f + 16.0f, + 100.0f,//1.0f + 100.0f,//0.0f + random()*45.0f, + -12.0f, + 8000.0f, + cgs.media.steamShader ); + + + for ( i = 0; i < 6; i++) + { + float width, length; + FXE_Spray( norm, 500, 175, 0.8f, vel);//, (FXPrimitive *) fx + length = 24.0 + random() * 12; + width = 0.5 + random() * 2; + FX_AddTrail( origin, vel, qtrue, length, -length, width, -width, + 1.0f, 1.0f, 0.5f, 2500.0f, cgs.media.orangeTrailShader);//RPG-X: RedTechie - Killtime use to be 1000.0f + + /*FX_AddTrail( origin, NULL, NULL, 16.0f, -15.0f, + 1.5, -1.5, 1.0f, 1.0f, 0.2f, 1000.0f, cgs.media.orangeTrailShader, rand() & FXF_BOUNCE ); +*/ + /*if ( fx == NULL ) + return;*/ + + + } + + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.grenadeAltExplodeSnd ); + + CG_ImpactMark( cgs.media.compressionMarkShader, origin, norm, random()*360, 1,1,1,1.0, qfalse, + random() * 16 + 48, qfalse ); + + CG_ExplosionEffects( origin, 2.0, 350 ); +} + +qboolean GrenadeBeep(localEntity_t *le) +{ + weaponInfo_t *weaponInfo = &cg_weapons[WP_GRENADE_LAUNCHER]; + + trap_S_StartSound(le->refEntity.origin, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->altHitSound); + return qtrue; +} + +/* +------------------------- +FX_GrenadeShrapnelBits +By: RedTechie - From SP +------------------------- +*/ + +/*void FX_BlowBits( vec3_t start, vec3_t end, vec3_t dir, vec3_t user ) +{ + vec3_t diff, org; + float len; +// FXLine *fx; + + VectorSubtract( end, start, diff ); + len = VectorNormalize( diff ) * ( 0.2 + random() * 0.3 ); + VectorMA( start, len, diff, org ); + + //fx = + FX_AddLine( end, start, (int)(random() * 3.2f), 2.0f + random() * 2, 0, 0.5f, 0.1f, 150 + random() * 150, cgs.media.orangeTrailShader ); + + //if ( fx == NULL ) + // return; + + //fx->SetFlags( FXF_SHRINK ); + + FX_AddQuad( end, dir, NULL, NULL, 1.0f, 64.0f, 1.0, 0.0, random() * 360.0f, 0.0f, 0.0, 200, cgs.media.orangeRingShader ); + // FX_AddQuad( end, dir, NULL, NULL, 20.0, -15.0, 0.6, 0.4, 0.0,0.0,0.0,450, cgs.media.borgEyeFlareShader ); +} +*/ +#define FX_GRENADE_ALT_STICK_TIME 2500 +void FX_GrenadeShrapnelBits( vec3_t start ) +{ + vec3_t zero = {0, 0, 0}; + // check G_MissileStick() to make sure this killtime coincides with that nextthink + FX_AddSpawner( start, zero, NULL, NULL, qfalse, 300, + 0, FX_GRENADE_ALT_STICK_TIME, GrenadeBeep, 10 ); +} + + +/* +------------------------- +FX_fxfunc_Explosion +------------------------- +*/ +void FX_fxfunc_Explosion( vec3_t start, vec3_t origin, vec3_t normal ) +{ + localEntity_t *le; + vec3_t dir; + vec3_t velocity; +// vec3_t end; +// trace_t trace; + float scale, dscale; + int i, j, numSparks; + //weaponInfo_t *weaponInfo = &cg_weapons[WP_COMPRESSION_RIFLE]; + //float scale, dscale; +// int s; +// vec3_t new_org; + + //Sparks + numSparks = 20 + (random() * 4.0f);//4 + + for ( i = 0; i < numSparks; i++ ) + { + scale = 0.25f + (random() * 1.0f); + dscale = -scale; + + //Randomize the direction + for (j = 0; j < 3; j ++ ) + { + dir[j] = normal[j] + (0.75 * crandom()); + } + + VectorNormalize(dir); + + //set the speed + VectorScale( dir, 200 + (50 * crandom()), velocity); + + le = FX_AddTrail( origin, + velocity, + qtrue, + 4.0f, + -4.0f, + scale, + -scale, + 1.0f, + 1.0f, + 0.5f, + 1000.0f, + cgs.media.sparkShader); + + } + + VectorMA( origin, 8, normal, dir ); + VectorSet(velocity, 0, 0, 8); + + // Smoke puffs + FX_AddSprite( dir, + velocity, + qfalse, + 20.0f + random()*60.0f,//2.2f + ( crandom() * 0.9f),//60.0f - random()*60.0f + 16.0f, + 100.0f,//1.0f + 100.0f,//0.0f + random()*45.0f, + -12.0f, + 8000.0f, + cgs.media.steamShader ); + + //Orient the explosions to face the camera + VectorSubtract( cg.refdef.vieworg, origin, dir ); + VectorNormalize( dir ); + + le = CG_MakeExplosion2( origin, dir, cgs.media.explosionModel, 5, cgs.media.electricalExplosionSlowShader, 475, qfalse, 2.2f + ( crandom() * 0.9f), LEF_NONE);//RPG-X: RedTechie - Scale use to be - 1.2f + ( crandom() * 0.3f) + le->light = 150; + le->refEntity.renderfx |= RF_NOSHADOW; + VectorSet( le->lightColor, 0.8f, 0.8f, 1.0f ); + + CG_ImpactMark( cgs.media.compressionMarkShader, origin, normal, random()*360, 1,1,1,1.0, qfalse, + random() * 16 + 48, qfalse ); + //CG_ImpactMark( cgs.media.compressionMarkShader, origin, normal, random()*360, 1,1,1,1, qfalse, 12, qfalse ); + + //Shake the camera + CG_ExplosionEffects( origin, 2, 400 ); + + // nice explosion sound at the point of impact + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.grenadeAltExplodeSnd ); + //trap_S_StartSound(origin, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->mainHitSound); +} + + +/* +------------------------- +FX_fxfunc_Shot +------------------------- +*/ +#define MAXRANGE_CRIFLE 8192 +void FX_fxfunc_Shot( vec3_t start, vec3_t dir ) +{ + vec3_t end; + trace_t trace; + + VectorMA(start, MAXRANGE_CRIFLE, dir, end); + CG_Trace( &trace, start, NULL, NULL, end, 0, MASK_SHOT ); + + //FX_CompressionExplosion(start, trace.endpos, trace.plane.normal, qfalse ); + FX_fxfunc_Explosion(start, trace.endpos, trace.plane.normal); +} diff --git a/cgame/fx_imod.c b/cgame/fx_imod.c new file mode 100644 index 0000000..05ab0a4 --- /dev/null +++ b/cgame/fx_imod.c @@ -0,0 +1,315 @@ +#include "cg_local.h" +#include "fx_local.h" + +/* +------------------------- +FX_AltIMODBolt +------------------------- +*/ + +/*void FX_AltIMODBolt( vec3_t start, vec3_t end, vec3_t dir ) +{ + vec3_t control1, control2, control1_vel, control2_vel, + control1_acceleration, control2_acceleration; + vec3_t direction, vr, vu; + float len; + localEntity_t *le; + + MakeNormalVectors( dir, vr, vu ); + + VectorSubtract( end, start, direction ); + len = VectorNormalize( direction ); + + VectorMA(start, len*0.5f, direction, control1 ); + VectorMA(start, len*0.25f, direction, control2 ); + + vectoangles( direction, control1_vel ); + control1_vel[ROLL] += crandom() * 360; + AngleVectors( control1_vel, NULL, NULL, control1_vel ); + + vectoangles( direction, control2_vel ); + control2_vel[ROLL] += crandom() * 360; + AngleVectors( control2_vel, NULL, NULL, control2_vel ); + + VectorScale(control1_vel, 12.0f + (140.0f * random()), control1_vel); + VectorScale(control2_vel, -12.0f + (-140.0f * random()), control2_vel); + + VectorClear(control1_acceleration); + VectorClear(control2_acceleration); + le = FX_AddBezier( start, end, + control1, control2, control1_vel, control2_vel, control1_acceleration, control2_acceleration, + 4.0f, //scale + 1000.0f, //killtime + cgs.media.IMOD2Shader ); + le->alpha = 0.8; + le->dalpha = -0.8; +}*/ + +/* +------------------------- +FX_IMODBolt2 +------------------------- +*/ + +/*void FX_IMODBolt2( vec3_t start, vec3_t end, vec3_t dir ) +{ + vec3_t control1, control2, control1_velocity, control2_velocity, + control1_acceleration, control2_acceleration; + float length = 0; + vec3_t vTemp; + localEntity_t *le; + + // initial position of control points + VectorSubtract(end, start, vTemp); + length = VectorNormalize(vTemp); + VectorMA(start, 0.5 * length, vTemp, control1); + VectorMA(start, 0.25 * length, vTemp, control2); + + // initial velocity of control points + vectoangles(vTemp, control1_velocity); + control1_velocity[ROLL] += crandom() * 360; + AngleVectors(control1_velocity, NULL, NULL, control1_velocity); + + vectoangles(vTemp, control2_velocity); + control2_velocity[ROLL] += crandom() * 360; + AngleVectors(control2_velocity, NULL, NULL, control2_velocity); + + VectorScale(control1_velocity, 12.0f + (140.0f * random()), control1_velocity); + VectorScale(control2_velocity, -12.0f + (-140.0f * random()), control2_velocity); + + // constant acceleration of control points + /* + VectorScale(control1_velocity, -1.2, control1_acceleration); + for (i = 0; i < 3; i++) + { + control1_acceleration[i] += flrandom (-10, 10); + } + VectorScale(control2_velocity, -1.2, control2_acceleration); + for (i = 0; i < 3; i++) + { + control2_acceleration[i] += flrandom (-10, 10); + } + */ +// VectorClear(control1_acceleration); +// VectorClear(control2_acceleration); +// +// le = FX_AddBezier(start, end, control1, control2, control1_velocity, control2_velocity, control1_acceleration, +// control2_acceleration, 4, 600, cgs.media.altIMOD2Shader); +// le->alpha = 0.6; +// le->dalpha = -0.6; +//}*/ + + +/* +------------------------- +FX_IMODShot +------------------------- +*/ + +//#define MAXRANGE_IMOD 8192 + +/*void FX_IMODShot( vec3_t end, vec3_t start, vec3_t dir) +{ + vec3_t ofs, end2; + trace_t trace; + qboolean render_impact = qtrue; + + VectorMA( end, 1, dir, ofs ); + + FX_AddLine(start, end, 1.0, 8.0, -8.0, 1.0, 0.0, 350, cgs.media.altIMODShader); + + FX_IMODBolt2( start, end, dir); + + // cover up the start point of the beam + FX_AddSprite( start, NULL, qfalse, irandom(8,12), -8, 1.0, 0.6, random()*360, 0.0, 400, cgs.media.purpleParticleShader); + // where do we put an explosion? + VectorMA(start, MAXRANGE_IMOD, cg.refdef.viewaxis[0], end2); + CG_Trace( &trace, start, NULL, NULL, end2, 0, MASK_SHOT ); + + if ( trace.surfaceFlags & SURF_NOIMPACT ) + { + render_impact = qfalse; + } + if ( render_impact ) + { + FX_IMODExplosion(end, trace.plane.normal); + } +}*/ + +/* +------------------------- +FX_AltIMODShot +------------------------- +*/ +//unused +/*#define FX_ALT_IMOD_HOLD 200 +#define FX_ALT_IMOD_FLASHSIZE 16 +qboolean IMODAltAftereffect(localEntity_t *le) +{ + localEntity_t *spr = NULL; + qhandle_t shader = cgs.media.blueParticleShader; + + //only want an initial sprite + le->endTime = cg.time; + + spr = FX_AddSprite( le->refEntity.origin,// origin + NULL, // velocity + qfalse, // gravity + FX_ALT_IMOD_FLASHSIZE, // scale + -10, // dscale + 0.8, // startalpha + 0.0, // endalpha + 0.0, // roll + 0.0, // elasticity + 700, // killTime + shader); // shader + + return qtrue; +}*/ + +/*void FX_AltIMODShot( vec3_t end, vec3_t start, vec3_t dir) +{ + vec3_t ofs, end2; + int i = 0; + trace_t trace; + qboolean render_impact = qtrue; + + VectorMA( end, 1, dir, ofs ); + + FX_AddLine( start, end, 1.0f, 32.0f, -32.0f, 1.0f, 1.0f, 500.0f, cgs.media.IMODShader); + + for ( i = 0; i < 2; i++ ) + FX_AltIMODBolt( start, end, dir ); + + // cover up the start point of the beam + FX_AddSprite( start, NULL, qfalse, FX_ALT_IMOD_FLASHSIZE, 0, 1.0, 0.6, 0.0, 0.0, FX_ALT_IMOD_HOLD, cgs.media.blueParticleShader); + FX_AddSpawner( start, dir, NULL, NULL, qfalse, FX_ALT_IMOD_HOLD, + 0, FX_ALT_IMOD_HOLD+100, IMODAltAftereffect, 10 ); + // where do we put an explosion? + VectorMA(start, MAXRANGE_IMOD, cg.refdef.viewaxis[0], end2); + CG_Trace( &trace, start, NULL, NULL, end2, 0, MASK_SHOT ); + + if ( trace.surfaceFlags & SURF_NOIMPACT ) + { + render_impact = qfalse; + } + if ( render_impact ) + { + FX_AltIMODExplosion(end, trace.plane.normal); + } +}*/ + +/* +------------------------- +FX_IMODExplosion +------------------------- +*/ + +/*void FX_IMODExplosion( vec3_t origin, vec3_t normal ) +{ + localEntity_t *le; + vec3_t direction, vel; + float scale, dscale; + int i, numSparks; + + //Orient the explosions to face the camera + VectorSubtract( cg.refdef.vieworg, origin, direction ); + VectorNormalize( direction ); + + //Tag the last one with a light + le = CG_MakeExplosion( origin, direction, cgs.media.explosionModel, cgs.media.imodExplosionShader, 400, qfalse); + le->light = 75; + VectorSet( le->lightColor, 1.0f, 0.8f, 0.5f ); + + //Sparks + numSparks = 3 + (rand() & 7); + // kef -- fixme. what does vel do? + VectorClear(vel); + + for ( i = 0; i < numSparks; i++ ) + { + scale = 1.0f + (random() * 0.5f); + dscale = -scale*0.5; + + FX_AddTrail( origin, + NULL, + qfalse, + 32.0f + (random() * 64.0f), + -256.0f, + scale, + 0.0f, + 1.0f, + 0.0f, + 0.25f, + 750.0f, + cgs.media.purpleParticleShader ); + + //FXE_Spray( normal, 500, 250, 0.75f, /*256,*///vel ); + //} + + //CG_ImpactMark( cgs.media.IMODMarkShader, origin, normal, random()*360, 1,1,1,0.75, qfalse, 5, qfalse ); +// CG_ImpactMark( cgs.media.scavMarkShader, origin, normal, random()*360, 1,1,1,0.2, qfalse, random() + 1, qfalse ); + +// CG_ExplosionEffects( origin, 1.0f, 150 ); +//}*/ + +/* +------------------------- +FX_AltIMODExplosion +------------------------- +*/ +/*void FX_AltIMODExplosion( vec3_t origin, vec3_t normal ) +{ + localEntity_t *le; + vec3_t direction, org, vel; + float scale, dscale; + int i, numSparks; + + //Orient the explosions to face the camera + VectorSubtract( cg.refdef.vieworg, origin, direction ); + VectorNormalize( direction ); + + //Tag the last one with a light + le = CG_MakeExplosion( origin, direction, cgs.media.explosionModel, cgs.media.electricalExplosionSlowShader, 475, qfalse); + le->light = 150; + VectorSet( le->lightColor, 1.0f, 0.8f, 0.5f ); + + for ( i = 0; i < 2; i ++) + { + VectorSet( org, origin[0] + 16 * random(), origin[1] + 16 * random(), origin[2] + 16 * random() ); + CG_MakeExplosion( org, direction, cgs.media.explosionModel, cgs.media.electricalExplosionFastShader, + 250, qfalse); + } + + //Sparks + + numSparks = 8 + (rand() & 7); + + // kef -- fixme. what does this vector do!?! waaaaaah! + VectorClear(vel); + + for ( i = 0; i < numSparks; i++ ) + { + scale = 1.5f + (random() * 0.5f); + dscale = -scale*0.5; + + FX_AddTrail( origin, + NULL, + qfalse, + 32.0f + (random() * 64.0f), + -256.0f, + scale, + 0.0f, + 1.0f, + 0.0f, + 0.25f, + 750.0f, + cgs.media.spark2Shader ); + + FXE_Spray( normal, 500, 250, 0.75f, /*256,*/ //vel ); + //} + + //CG_ImpactMark( cgs.media.IMODMarkShader, origin, normal, random()*360, 1,1,1,1, qfalse, 8, qfalse ); + + //CG_ExplosionEffects( origin, 2.0f, 250 ); +//}*/ diff --git a/cgame/fx_item.c b/cgame/fx_item.c new file mode 100644 index 0000000..e35b254 --- /dev/null +++ b/cgame/fx_item.c @@ -0,0 +1,306 @@ +#include "cg_local.h" +#include "fx_local.h" + + + +// +// detpack +// + +#define NUM_RING_SHADERS 6 + +qboolean DetpackAftereffect(localEntity_t *le) +{ + localEntity_t *cyl = NULL; + qhandle_t shader = cgs.media.phaserShader; + qhandle_t slowRingShaders[NUM_RING_SHADERS]; + float percentLife = 1.0 - (le->endTime - cg.time)*le->lifeRate; + float alpha = 0.6 - (0.6*percentLife*percentLife); + // data for shell + float shellLife = percentLife + .2; + float height1 = 20 + (percentLife * 150); + float height2 =(50*percentLife); + float scale1 = 40 + (percentLife * 1500); + float scale2 = 20 + (percentLife * 1200); + // data for flat energy rings + float ringLife = percentLife + .5; + float scale3 = 200 + (percentLife * 3400); + float scale4 = 100 + (percentLife * 3000); + float scale5 = 20 + (percentLife * 1000); + float scale6 = 10 + (percentLife * 200); + float ringAlpha = 0.6 - (0.6*ringLife*ringLife); + vec3_t up = {0,0,1},origin1; + + + slowRingShaders[0] = cgs.media.testDetpackRingShader1; + slowRingShaders[1] = cgs.media.testDetpackRingShader2; + slowRingShaders[2] = cgs.media.testDetpackRingShader3; + slowRingShaders[3] = cgs.media.testDetpackRingShader4; + slowRingShaders[4] = cgs.media.testDetpackRingShader5; + slowRingShaders[5] = cgs.media.testDetpackRingShader6; + + // slower, inner ring + VectorCopy(le->refEntity.origin, origin1); + if (NUM_RING_SHADERS == le->data.spawner.data1) + { + le->data.spawner.data1 = 0; + } + else if (le->data.spawner.data1 < 0) + { + le->data.spawner.data1 = 0; + } + shader = slowRingShaders[le->data.spawner.data1++]; + // fast, outer ring + cyl = FX_AddCylinder( origin1, + up, + 0.1,// height, + 0,// dheight, + scale5,// scale, + 0,// dscale, + scale6,// scale2, + 0,// dscale2, + ringAlpha,// startalpha, + 0.0,// endalpha, + 500,// killTime, + shader, + 15);// bias ); + cyl->leFlags |= LEF_ONE_FRAME; + + if (shellLife <= 1.0f) + { + origin1[2] += height2; + shader = cgs.media.phaserShader; + cyl = FX_AddCylinder( origin1, + up, + height1,// height, + 0,// dheight, + scale1,// scale, + 0,// dscale, + scale2,// scale2, + 0,// dscale2, + alpha,// startalpha, + 0.0,// endalpha, + 500,// killTime, + shader, + 15);// bias ); + cyl->leFlags |= LEF_ONE_FRAME; + + cyl = FX_AddCylinder( le->refEntity.origin, + up, + height2, // height, + 0, // dheight, + scale1, // scale, + 0, // dscale, + scale1, // scale2, + 0, // dscale2, + alpha, // startalpha, + 0.0, // endalpha, + 500, // killTime, + shader, + 15); // bias ); + cyl->leFlags |= LEF_ONE_FRAME; + } + // flat energy wave thingy + if (ringLife <= 1.0f) + { + shader = cgs.media.testDetpackShader3; + VectorCopy(le->refEntity.origin, origin1); + // fast, outer ring + + cyl = FX_AddCylinder( origin1, + up, + 0.1,// height, + 0,// dheight, + scale3,// scale, + 0,// dscale, + scale4,// scale2, + 0,// dscale2, + ringAlpha,// startalpha, + 0.0,// endalpha, + 500,// killTime, + shader, + 15);// bias ); + cyl->leFlags |= LEF_ONE_FRAME; + } + return qtrue; +} + + +void FX_Detpack(vec3_t origin) +{ + localEntity_t *le; + qhandle_t null; + vec3_t direction, org, vel, norm = {0,0,1}; + int i; + + VectorCopy( norm, direction); + + // Add an explosion and tag a light to it + le = CG_MakeExplosion2( origin, direction, cgs.media.nukeModel, 5, null, 250, qfalse, 100.0f, LEF_FADE_RGB); + le->light = 300; + le->refEntity.renderfx |= RF_NOSHADOW; + + VectorSet( le->lightColor, 1.0f, 0.6f, 0.2f ); + + // Ground ring +// FX_AddQuad( origin, norm, 5, 150, 1.0, 0.0, random() * 360, 600, cgs.media.bigShockShader ); + // Flare + VectorMA( origin, 12, direction, org ); + FX_AddSprite( org, NULL, qfalse, 160.0, -160.0, 1.0, 0.0, 0.0, 0.0, 500, cgs.media.sunnyFlareShader );//, FXF_NON_LINEAR_FADE ); + + for (i = 0; i < 12; i++) + { + float width, length; + FXE_Spray( norm, 470, 325, 0.5f, vel); + length = 50.0 + random() * 12; + width = 1.5 + random() * 2; + FX_AddTrail( origin, vel, qtrue, length, -length, width, -width, + 1.0f, 1.0f, 0.5f, 1000.0f, cgs.media.orangeTrailShader); + } + +// trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.detpackExplodeSound ); + + // Smoke and impact + CG_ImpactMark( cgs.media.compressionMarkShader, origin, norm, random()*360, 1,1,1,1.0, qfalse, + random() * 16 + 48, qfalse ); + + // mondo explosion shock wave cloud thing + le = FX_AddSpawner( origin, norm, NULL, NULL, qfalse, 0, + 0, 1500, DetpackAftereffect, 10 ); + le->data.spawner.data1 = 0; + + // shake absolutely _everyone_ + CG_ExplosionEffects(origin, 5.0f, 8092); +} + + +// +// portable shield +// + +//RPG-X ToDo: Modify force field Code Here +void FX_DrawPortableShield(centity_t *cent) +{ + int xaxis, height, posWidth, negWidth, team; // light; + vec3_t start, end, normal; + //vec4_t RGBA; + float halfHeight; + localEntity_t *le; + qhandle_t shader; + + if (cent->currentState.eFlags & EF_NODRAW) + { + return; + } + + // decode the data stored in time2 + //pos = ((cent->currentState.time2 >> 32) & 1); + //vert = ((cent->currentState.time2 >> 31) & 1); + xaxis = ((cent->currentState.time2 >> 30) & 1); //24 + height = ((cent->currentState.time2 >> 20) & 1023); //16 + posWidth = ((cent->currentState.time2 >> 10) & 1023); //8 + negWidth = (cent->currentState.time2 & 1023); + + team = (cent->currentState.otherEntityNum2); + halfHeight = (float)height * .5; + + /*if ( !vert ) + {*/ + VectorCopy(cent->lerpOrigin, start); + VectorCopy(cent->lerpOrigin, end); + start[2] += halfHeight; + end[2] += halfHeight; + + VectorClear(normal); + if (xaxis) // drawing along x-axis + { + start[0] -= negWidth; + end[0] += posWidth; + normal[1] = 1; + } + else + { + start[1] -= negWidth; + end[1] += posWidth; + normal[0] = 1; + } + //} + //else + //{ + // VectorCopy(cent->lerpOrigin, start); + // VectorCopy(cent->lerpOrigin, end); + // if ( xaxis ) { + // start[1] += halfHeight; + // end[1] += halfHeight; + // } + // else + // { + // start[0] += halfHeight; + // end[0] += halfHeight; + // } + + // VectorClear(normal); + // if (xaxis) // drawing along x-axis + // { + // start[0] -= negWidth; + // end[0] += posWidth; + // normal[2] = 1; + // } + // else + // { + // start[1] -= negWidth; + // end[1] += posWidth; + // normal[2] = 1; + // } + //} + // draw a rectangle o' shieldness +/* + if (team == TEAM_RED) + { + if (cent->currentState.eFlags & EF_ITEMPLACEHOLDER) + { // Damaged. + shader = cgs.media.shieldDamageShaderRed; + } + else + { + shader = cgs.media.shieldActivateShaderRed; + } + } + else + {*/ + + //TiM - Show the forcefield when the place flag is active only + //This way, we canhave it flare on events, and invisible the rest of the time + + //tho make sure admins can see it + if((int)cent->currentState.origin2[0] == 1) { + shader = cgs.media.shieldActivateShaderBorg; + } + else if((int)cent->currentState.origin2[0] == 2) { + shader = cgs.media.shieldActivateShaderYellow; + } + else if((int)cent->currentState.origin2[0] == 3) { + shader = cgs.media.shieldActivateShaderRed; + } + else { + shader = cgs.media.shieldActivateShaderBlue; + } + if ( cent->currentState.eFlags & EF_ITEMPLACEHOLDER || cgs.clientinfo[cg.snap->ps.clientNum].isAdmin/*cg.snap->ps.persistant[PERS_CLASS] == PC_ADMIN*/ ) + le = FX_AddOrientedLine(start, end, normal, 1.0f, height, 0.0f, 1.0f, 1.0f, 50.0, shader); + //TiM + //if (cent->currentState.eFlags & EF_ITEMPLACEHOLDER) + //{ // Damaged. + // shader = cgs.media.shieldDamageShaderBlue; + //} + //else + //{ + // shader = cgs.media.shieldActivateShaderBlue; + //} + //- +// } + + //le = FX_AddOrientedLine(start, end, normal, 1.0f, height, 0.0f, 1.0f, 1.0f, 50.0, shader); +// le->leFlags |= LEF_ONE_FRAME; +} + + diff --git a/cgame/fx_lib.c b/cgame/fx_lib.c new file mode 100644 index 0000000..3d41c45 --- /dev/null +++ b/cgame/fx_lib.c @@ -0,0 +1,970 @@ +// FX Library + +#include "cg_local.h" + +void FXE_Spray (vec3_t direction, float speed, float variation, float cone, vec3_t velocity) +{ + vec3_t dir; + int i; + + //Randomize the direction + for (i = 0; i < 3; i ++ ) + { + dir[i] = direction[i] + (cone * crandom()); + } + + VectorNormalize(dir); + + //set the speed + VectorScale( dir, speed + (variation * crandom()), velocity); +} + + + +localEntity_t *FX_AddLine(vec3_t start, vec3_t end, float stScale, float scale, float dscale, float startalpha, float endalpha, float killTime, qhandle_t shader) +{ + localEntity_t *le; + +#ifdef _DEBUG + if (!shader) + { + Com_Printf("FX_AddLine: NULL shader\n"); + } +#endif + + le = CG_AllocLocalEntity(); + le->leType = LE_LINE; + + le->startTime = cg.time; + le->endTime = le->startTime + killTime; + le->data.line.width = scale; + le->data.line.dwidth = dscale; + + le->alpha = startalpha; + le->dalpha = endalpha - startalpha; + + le->refEntity.data.line.stscale = stScale; + le->refEntity.data.line.width = scale; + + le->refEntity.customShader = shader; + + // set origin + VectorCopy ( start, le->refEntity.origin); + VectorCopy ( end, le->refEntity.oldorigin ); + + AxisClear(le->refEntity.axis); + le->refEntity.shaderRGBA[0] = 0xff; + le->refEntity.shaderRGBA[1] = 0xff; + le->refEntity.shaderRGBA[2] = 0xff; + le->refEntity.shaderRGBA[3] = 0xff; + + le->color[0] = 1.0; + le->color[1] = 1.0; + le->color[2] = 1.0; + le->color[3] = 1.0; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + return(le); +} + + + +localEntity_t *FX_AddLine2(vec3_t start, vec3_t end, float stScale, float width1, float dwidth1, float width2, float dwidth2, + float startalpha, float endalpha, vec3_t startRGB, vec3_t endRGB, float killTime, qhandle_t shader) +{ + localEntity_t *le; + +#ifdef _DEBUG + if (!shader) + { + Com_Printf("FX_AddLine2: NULL shader\n"); + } +#endif + + le = CG_AllocLocalEntity(); + le->leType = LE_LINE2; + + le->startTime = cg.time; + le->endTime = le->startTime + killTime; + le->data.line2.width = width1; + le->data.line2.dwidth = dwidth1; + le->data.line2.width2 = width2; + le->data.line2.dwidth2 = dwidth2; + + le->alpha = startalpha; + le->dalpha = endalpha - startalpha; + VectorCopy(startRGB, le->data.line2.startRGB); + VectorSubtract(endRGB, startRGB, le->data.line2.dRGB); + + le->refEntity.data.line.stscale = stScale; + le->refEntity.data.line.width = width1; + le->refEntity.data.line.width2 = width2; + + le->refEntity.customShader = shader; + + // set origin + VectorCopy ( start, le->refEntity.origin); + VectorCopy ( end, le->refEntity.oldorigin ); + + AxisClear(le->refEntity.axis); + le->refEntity.shaderRGBA[0] = 0xff; + le->refEntity.shaderRGBA[1] = 0xff; + le->refEntity.shaderRGBA[2] = 0xff; + le->refEntity.shaderRGBA[3] = 0xff; + + le->color[0] = startRGB[0]; + le->color[1] = startRGB[1]; + le->color[2] = startRGB[2]; + le->color[3] = startalpha; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + return(le); +} + +localEntity_t *FX_AddOrientedLine(vec3_t start, vec3_t end, vec3_t normal, float stScale, float scale, + float dscale, float startalpha, float endalpha, float killTime, qhandle_t shader) +{ + localEntity_t *le; + +#ifdef _DEBUG + if (!shader) + { + Com_Printf("FX_AddLine: NULL shader\n"); + } +#endif + + le = CG_AllocLocalEntity(); + le->leType = LE_OLINE; + + le->startTime = cg.time; + le->endTime = le->startTime + killTime; + le->data.line.width = scale; + le->data.line.dwidth = dscale; + + le->alpha = startalpha; + le->dalpha = endalpha - startalpha; + + le->refEntity.data.line.stscale = stScale; + le->refEntity.data.line.width = scale; + + le->refEntity.customShader = shader; + + // set origin + VectorCopy ( start, le->refEntity.origin); + VectorCopy ( end, le->refEntity.oldorigin ); + + AxisClear(le->refEntity.axis); + VectorCopy( normal, le->refEntity.axis[0] ); + RotateAroundDirection( le->refEntity.axis, 0); // le->refEntity.data.sprite.rotation ); This is roll in quad land + + le->refEntity.shaderRGBA[0] = 0xff; + le->refEntity.shaderRGBA[1] = 0xff; + le->refEntity.shaderRGBA[2] = 0xff; + le->refEntity.shaderRGBA[3] = 0xff; + + le->color[0] = 1.0; + le->color[1] = 1.0; + le->color[2] = 1.0; + le->color[3] = 1.0; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + return(le); +} + +localEntity_t *FX_AddTrail( vec3_t origin, vec3_t velocity, qboolean gravity, float length, float dlength, + float scale, float dscale, float startalpha, float endalpha, + float elasticity, float killTime, qhandle_t shader) +{ + localEntity_t *le; + +#ifdef _DEBUG + if (!shader) + { + Com_Printf("FX_AddTrail: NULL shader\n"); + } +#endif + + le = CG_AllocLocalEntity(); + le->leType = LE_TRAIL; + + le->startTime = cg.time; + le->endTime = le->startTime + killTime; + + le->data.trail.width = scale; + le->data.trail.dwidth = dscale; + le->data.trail.length = length; + le->data.trail.dlength = dlength; + + le->alpha = startalpha; + le->dalpha = endalpha - startalpha; + VectorSet(le->data.trail.startRGB, 1, 1, 1); + VectorSet(le->data.trail.dRGB, 0, 0, 0); + + le->refEntity.data.line.stscale = 1.0; + le->refEntity.data.line.width = scale; + + le->refEntity.customShader = shader; + + // set origin + VectorCopy ( origin, le->refEntity.origin); + // kef -- extrapolate oldorigin based on length of trail and origin? + if (velocity) + { + vec3_t vel; + VectorNormalize2(velocity, vel); + VectorMA(origin, -length, vel, le->refEntity.oldorigin); + } + else + { + VectorCopy ( origin, le->refEntity.oldorigin ); + } + + AxisClear(le->refEntity.axis); + le->refEntity.shaderRGBA[0] = 0xff; + le->refEntity.shaderRGBA[1] = 0xff; + le->refEntity.shaderRGBA[2] = 0xff; + le->refEntity.shaderRGBA[3] = 0xff*startalpha; + + le->color[0] = 1.0; + le->color[1] = 1.0; + le->color[2] = 1.0; + le->color[3] = 1.0; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + if (velocity) + { + le->leFlags |= LEF_MOVE; + VectorCopy (origin, le->pos.trBase); + VectorCopy (velocity, le->pos.trDelta); + if (gravity) + le->pos.trType = TR_GRAVITY; + else + le->pos.trType = TR_LINEAR; + le->pos.trTime = cg.time; + le->pos.trDuration = killTime; + + if (elasticity > 0) + { + le->leFlags |= LEF_USE_COLLISION; + le->bounceFactor = elasticity; + } + } + + return(le); +} + + + +localEntity_t *FX_AddTrail2( vec3_t origin, vec3_t velocity, qboolean gravity, float length, float dlength, + float scale, float dscale, float startalpha, float endalpha, vec3_t startRGB, vec3_t endRGB, + float elasticity, float killTime, qhandle_t shader) +{ + localEntity_t *le; + +#ifdef _DEBUG + if (!shader) + { + Com_Printf("FX_AddTrail: NULL shader\n"); + } +#endif + + le = CG_AllocLocalEntity(); + le->leType = LE_TRAIL; + + le->startTime = cg.time; + le->endTime = le->startTime + killTime; + + le->data.trail.width = scale; + le->data.trail.dwidth = dscale; + le->data.trail.length = length; + le->data.trail.dlength = dlength; + + le->alpha = startalpha; + le->dalpha = endalpha - startalpha; + VectorCopy(startRGB, le->data.trail.startRGB); + VectorSubtract(endRGB, startRGB, le->data.trail.dRGB); + + le->refEntity.data.line.stscale = 1.0; + le->refEntity.data.line.width = scale; + + le->refEntity.customShader = shader; + + // set origin + VectorCopy ( origin, le->refEntity.origin); + // kef -- extrapolate oldorigin based on length of trail and origin? + if (velocity) + { + vec3_t vel; + VectorNormalize2(velocity, vel); + VectorMA(origin, -length, vel, le->refEntity.oldorigin); + } + else + { + VectorCopy ( origin, le->refEntity.oldorigin ); + } + + AxisClear(le->refEntity.axis); + le->refEntity.shaderRGBA[0] = 0xff*startRGB[0]; + le->refEntity.shaderRGBA[1] = 0xff*startRGB[1]; + le->refEntity.shaderRGBA[2] = 0xff*startRGB[2]; + le->refEntity.shaderRGBA[3] = 0xff*startalpha; + + le->color[0] = 1.0; + le->color[1] = 1.0; + le->color[2] = 1.0; + le->color[3] = 1.0; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + if (velocity) + { + le->leFlags |= LEF_MOVE; + VectorCopy (origin, le->pos.trBase); + VectorCopy (velocity, le->pos.trDelta); + if (gravity) + le->pos.trType = TR_GRAVITY; + else + le->pos.trType = TR_LINEAR; + le->pos.trTime = cg.time; + le->pos.trDuration = killTime; + + if (elasticity > 0) + { + le->leFlags |= LEF_USE_COLLISION; + le->bounceFactor = elasticity; + } + } + + return(le); +} + + +/* +=============== +FX_AddSprite + +Adds a view oriented sprite to the FX wrapper render list +=============== +*/ + +localEntity_t *FX_AddSprite(vec3_t origin, vec3_t velocity, qboolean gravity, float scale, float dscale, + float startalpha, float endalpha, float roll, float elasticity, + float killTime, qhandle_t shader) +{ + localEntity_t *le; + +#ifdef _DEBUG + if (!shader) + { + Com_Printf("FX_AddSprite: NULL shader\n"); + } +#endif + + // Glow mark + + le = CG_AllocLocalEntity(); + le->leType = LE_VIEWSPRITE; + le->refEntity.data.sprite.rotation = roll; + + le->startTime = cg.time; + le->endTime = le->startTime + killTime; + + le->data.sprite.radius = scale; + le->data.sprite.dradius = dscale; + + le->alpha = startalpha; + le->dalpha = endalpha - startalpha; + VectorSet(le->data.sprite.startRGB, 1, 1, 1); + VectorSet(le->data.sprite.dRGB, 0, 0, 0); + +// le->refEntity.hModel = 0; + le->refEntity.customShader = shader; + + // set origin + VectorCopy ( origin, le->refEntity.origin); + VectorCopy ( origin, le->refEntity.oldorigin ); + + le->color[0] = 1.0; + le->color[1] = 1.0; + le->color[2] = 1.0; + le->color[3] = startalpha; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + if (velocity) + { + le->leFlags |= LEF_MOVE; + VectorCopy (origin, le->pos.trBase); + VectorCopy (velocity, le->pos.trDelta); + if (gravity) + le->pos.trType = TR_GRAVITY; + else + le->pos.trType = TR_LINEAR; + le->pos.trTime = cg.time; + le->pos.trDuration = killTime; + + if (elasticity > 0) + { + le->leFlags |= LEF_USE_COLLISION; + le->bounceFactor = elasticity; + } + } + + return(le); +} + + +/* +=============== +FX_AddSprite2 + +Adds a view oriented sprite to the FX wrapper render list +=============== +*/ + +localEntity_t *FX_AddSprite2(vec3_t origin, vec3_t velocity, qboolean gravity, float scale, float dscale, + float startalpha, float endalpha, vec3_t startRGB, vec3_t endRGB, float roll, float elasticity, + float killTime, qhandle_t shader) +{ + localEntity_t *le; + +#ifdef _DEBUG + if (!shader) + { + Com_Printf("FX_AddSprite: NULL shader\n"); + } +#endif + + // Glow mark + + le = CG_AllocLocalEntity(); + le->leType = LE_VIEWSPRITE; + le->refEntity.data.sprite.rotation = roll; + + le->startTime = cg.time; + le->endTime = le->startTime + killTime; + + le->data.sprite.radius = scale; + le->data.sprite.dradius = dscale; + + le->alpha = startalpha; + le->dalpha = endalpha - startalpha; + VectorCopy(startRGB, le->data.sprite.startRGB); + VectorSubtract(endRGB, startRGB, le->data.sprite.dRGB); + +// le->refEntity.hModel = 0; + le->refEntity.customShader = shader; + + // set origin + VectorCopy ( origin, le->refEntity.origin); + VectorCopy ( origin, le->refEntity.oldorigin ); + + le->color[0] = startRGB[0]; + le->color[1] = startRGB[1]; + le->color[2] = startRGB[2]; + le->color[3] = startalpha; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + if (velocity) + { + le->leFlags |= LEF_MOVE; + VectorCopy (origin, le->pos.trBase); + VectorCopy (velocity, le->pos.trDelta); + if (gravity) + le->pos.trType = TR_GRAVITY; + else + le->pos.trType = TR_LINEAR; + le->pos.trTime = cg.time; + le->pos.trDuration = killTime; + + if (elasticity > 0) + { + le->leFlags |= LEF_USE_COLLISION; + le->bounceFactor = elasticity; + } + } + + return(le); +} + +/* +=============== +FX_AddBezier + +Adds a Bezier curve to the FX wrapper render list +=============== +*/ + +localEntity_t *FX_AddBezier(vec3_t start, vec3_t end, vec3_t cpoint1, vec3_t cpoint2, vec3_t cpointvel1, vec3_t cpointvel2, + vec3_t cpointacc1, vec3_t cpointacc2, float width, float killTime, qhandle_t shader) +{ + localEntity_t *le = CG_AllocLocalEntity(); + +#ifdef _DEBUG + if (!shader) + { + Com_Printf("FX_AddBezier: NULL shader\n"); + } +#endif + + // just testing beziers + le->leType = LE_BEZIER; + + le->startTime = cg.time; + le->endTime = le->startTime + killTime; + le->data.line.width = width; + + le->alpha = 1.0; + le->dalpha = -1.0; + + le->refEntity.customShader = shader; + + // set origin + VectorCopy ( start, le->refEntity.origin); + VectorCopy ( end, le->refEntity.oldorigin ); + + AxisClear(le->refEntity.axis); + le->refEntity.shaderRGBA[0] = 0xff; + le->refEntity.shaderRGBA[1] = 0xff; + le->refEntity.shaderRGBA[2] = 0xff; + le->refEntity.shaderRGBA[3] = 0xff; + + le->color[0] = 1.0; + le->color[1] = 1.0; + le->color[2] = 1.0; + le->color[3] = 1.0; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + if (cpoint1) + { + VectorCopy(cpoint1, le->data.line.control1); + } + if (cpoint2) + { + VectorCopy(cpoint2, le->data.line.control2); + } + if (cpointvel1) + { + VectorCopy(cpointvel1, le->data.line.control1_velocity); + } + if (cpointvel2) + { + VectorCopy(cpointvel2, le->data.line.control2_velocity); + } + if (cpointacc1) + { + VectorCopy(cpointacc1, le->data.line.control1_acceleration); + } + if (cpointacc2) + { + VectorCopy(cpointacc2, le->data.line.control2_acceleration); + } + + return le; +} + +/* +=============== +FX_AddQuad + +Adds a quad to the FX wrapper render list +=============== +*/ + +localEntity_t *FX_AddQuad( vec3_t origin, vec3_t normal, float scale, float dscale, + float startalpha, float endalpha, float roll, float killTime, qhandle_t shader ) +{ + localEntity_t *le = CG_AllocLocalEntity(); + +#ifdef _DEBUG + if (!shader) + { + Com_Printf("FX_AddQuad: NULL shader\n"); + } +#endif + + le->leType = LE_QUAD; + le->refEntity.data.sprite.rotation = roll; + + le->startTime = cg.time; + le->endTime = le->startTime + killTime; + + le->data.sprite.radius = scale; + le->data.sprite.dradius = dscale; + + le->alpha = startalpha; + le->dalpha = endalpha - startalpha; + VectorSet(le->data.sprite.startRGB, 1, 1, 1); + VectorSet(le->data.sprite.dRGB, 0, 0, 0); + +// le->refEntity.hModel = 0; + le->refEntity.customShader = shader; + + // set origin + VectorCopy ( origin, le->refEntity.origin); + VectorCopy ( origin, le->refEntity.oldorigin ); + + VectorCopy( normal, le->refEntity.axis[0] ); + RotateAroundDirection( le->refEntity.axis, le->refEntity.data.sprite.rotation ); + + le->color[0] = 1.0; + le->color[1] = 1.0; + le->color[2] = 1.0; + le->color[3] = startalpha; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + return(le); +} + + + +/* +=============== +FX_AddQuad2 + +Adds a quad to the FX wrapper render list +=============== +*/ + +localEntity_t *FX_AddQuad2( vec3_t origin, vec3_t normal, float scale, float dscale, float startalpha, float endalpha, + vec3_t startRGB, vec3_t endRGB, float roll, float killTime, qhandle_t shader ) +{ + localEntity_t *le = CG_AllocLocalEntity(); + +#ifdef _DEBUG + if (!shader) + { + Com_Printf("FX_AddQuad: NULL shader\n"); + } +#endif + + le->leType = LE_QUAD; + le->refEntity.data.sprite.rotation = roll; + + le->startTime = cg.time; + le->endTime = le->startTime + killTime; + + le->data.sprite.radius = scale; + le->data.sprite.dradius = dscale; + VectorCopy(startRGB, le->data.sprite.startRGB); + VectorSubtract(endRGB, startRGB, le->data.sprite.dRGB); + + le->alpha = startalpha; + le->dalpha = endalpha - startalpha; + +// le->refEntity.hModel = 0; + le->refEntity.customShader = shader; + + // set origin + VectorCopy ( origin, le->refEntity.origin); + VectorCopy ( origin, le->refEntity.oldorigin ); + + VectorCopy( normal, le->refEntity.axis[0] ); + RotateAroundDirection( le->refEntity.axis, le->refEntity.data.sprite.rotation ); + + le->color[0] = startRGB[0]; + le->color[1] = startRGB[1]; + le->color[2] = startRGB[2]; + le->color[3] = startalpha; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + return(le); +} + + + +/* +=============== +FX_AddCylinder + +Adds a cylinder to the FX wrapper render list +Overloaded for RGB +=============== +*/ + +//NOTENOTE: The reigning king of parameters! +#define DEFAULT_ST_SCALE 1.0f + +localEntity_t *FX_AddCylinder( vec3_t start, + vec3_t normal, + float height, + float dheight, + float scale, + float dscale, + float scale2, + float dscale2, + float startalpha, + float endalpha, + float killTime, + qhandle_t shader, + float bias ) +{ + localEntity_t *le = CG_AllocLocalEntity(); + +#ifdef _DEBUG + if (!shader) + { + Com_Printf("FX_AddCylinder: NULL shader\n"); + } +#endif + + le->leType = LE_CYLINDER; + le->refEntity.data.cylinder.height = height; + le->refEntity.data.cylinder.width = scale; + le->refEntity.data.cylinder.width2 = scale2; + + le->startTime = cg.time; + le->endTime = le->startTime + killTime; + + le->data.cylinder.height = height; + le->data.cylinder.dheight = dheight; + le->data.cylinder.width = scale; + le->data.cylinder.dwidth = dscale; + le->data.cylinder.width2 = scale2; + le->data.cylinder.dwidth2 = dscale2; + + le->alpha = startalpha; + le->dalpha = endalpha - startalpha; + + le->refEntity.customShader = shader; + + le->refEntity.data.cylinder.bias = bias; + le->refEntity.data.cylinder.stscale = 1.0; + le->refEntity.data.cylinder.wrap = qtrue; + + // set origin + VectorCopy ( start, le->refEntity.origin); + VectorCopy ( start, le->refEntity.oldorigin ); + + VectorCopy( normal, le->refEntity.axis[0] ); + RotateAroundDirection( le->refEntity.axis, 0); + + le->color[0] = 1.0; + le->color[1] = 1.0; + le->color[2] = 1.0; + le->color[3] = startalpha; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + return(le); +} + + + +/* +=============== +FX_AddElectricity + +Adds a electricity bolt to the scene +=============== +*/ + +localEntity_t *FX_AddElectricity( vec3_t origin, vec3_t origin2, float stScale, float scale, float dscale, + float startalpha, float endalpha, float killTime, qhandle_t shader, float deviation ) +{ + localEntity_t *le; + +#ifdef _DEBUG + if (!shader) + { + Com_Printf("FX_AddElectricity: NULL shader\n"); + } +#endif + + le = CG_AllocLocalEntity(); + le->leType = LE_ELECTRICITY; + + // set origin + VectorCopy (origin, le->refEntity.origin); + VectorCopy (origin2, le->refEntity.oldorigin ); + + le->refEntity.data.electricity.stscale = stScale; + le->refEntity.data.electricity.deviation = deviation; + le->data.electricity.width = scale; + le->data.electricity.dwidth = dscale; + + le->alpha = startalpha; + le->dalpha = endalpha - startalpha; + + le->startTime = cg.time; + le->endTime = le->startTime + killTime; + + le->refEntity.customShader = shader; + + le->color[0] = 1.0; + le->color[1] = 1.0; + le->color[2] = 1.0; + le->color[3] = startalpha; + + return(le); +} +/* +=============== +FX_AddParticle + +Adds a particle (basically, a sprite with an optional think function) to the FX wrapper render list +=============== +*/ + +localEntity_t *FX_AddParticle( vec3_t origin, vec3_t velocity, qboolean gravity, float scale, float dscale, + float startalpha, float endalpha, float roll, float elasticity, + float killTime, qhandle_t shader, qboolean (*thinkFn)(localEntity_t *le) ) +{ + localEntity_t *le; + +#ifdef _DEBUG + if (!shader) + { + Com_Printf("FX_AddParticle: NULL shader\n"); + } +#endif + + // Glow mark + + le = CG_AllocLocalEntity(); + le->leType = LE_PARTICLE; + le->refEntity.data.sprite.rotation = roll; + + le->startTime = cg.time; + le->endTime = le->startTime + killTime; + + le->data.particle.radius = scale; + le->data.particle.dradius = dscale; + + le->alpha = startalpha; + le->dalpha = endalpha - startalpha; + +// le->refEntity.hModel = 0; + le->refEntity.customShader = shader; + + // set origin + VectorCopy ( origin, le->refEntity.origin); + VectorCopy ( origin, le->refEntity.oldorigin ); + + le->color[0] = 1.0; + le->color[1] = 1.0; + le->color[2] = 1.0; + le->color[3] = startalpha; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + // wacky think function stuff + le->data.particle.thinkFn = thinkFn; + + if (velocity) + { + le->leFlags |= LEF_MOVE; + VectorCopy (origin, le->pos.trBase); + VectorCopy (velocity, le->pos.trDelta); + if (gravity) + le->pos.trType = TR_GRAVITY; + else + le->pos.trType = TR_LINEAR; + le->pos.trTime = cg.time; + le->pos.trDuration = killTime; + + if (elasticity > 0) + { + le->leFlags |= LEF_USE_COLLISION; + le->bounceFactor = elasticity; + } + } + + return(le); +} + +/* +=============== +FX_AddSpawner + +Adds a spawner -- basically, a local entity with a think function. Spawners don't have any rendered entities +associated with them inherently, but the spawner's think fn probably generates them. +=============== +*/ +localEntity_t *FX_AddSpawner( vec3_t origin, vec3_t dir, vec3_t velocity, vec3_t user, qboolean gravity, int delay, + float variance, float killTime, qboolean (*thinkFn)(localEntity_t *le), int radius ) +{ + localEntity_t *le = NULL; + + if (NULL == thinkFn) + { + // a spawner with no think fn is silly. and useless. + return NULL; + } + le = CG_AllocLocalEntity(); + + le->leType = LE_SPAWNER; + + le->data.spawner.data1 = radius; + le->data.spawner.delay = delay; + le->data.spawner.nextthink = cg.time + delay; + le->startTime = cg.time; + // if we want the spawner to hang around forever, we use a killtime of 0 and the think fn keeps adjusting it. + //thing is, we still need it to not get culled right here, so give it an arbitrary endTime somewhere in the future. + if (0 == killTime) + { + le->endTime = le->startTime + 10000; + le->data.spawner.dontDie = qtrue; + } + else + { + le->endTime = le->startTime + killTime; + } + + le->data.spawner.variance = variance; + if(dir) + VectorCopy(dir, le->data.spawner.dir); + // set origin + VectorCopy ( origin, le->refEntity.origin); + VectorCopy ( origin, le->refEntity.oldorigin ); + + // maybe employ the user variable here, like in singleplayer? or in the think fn? + le->color[0] = 1.0; + le->color[1] = 1.0; + le->color[2] = 1.0; + le->color[3] = 1.0; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + // wacky think function stuff + le->data.spawner.thinkFn = thinkFn; + + if (velocity) + { + le->leFlags |= LEF_MOVE; + VectorCopy (origin, le->pos.trBase); + if(velocity) + VectorCopy (velocity, le->pos.trDelta); + if (gravity) + le->pos.trType = TR_GRAVITY; + else + le->pos.trType = TR_LINEAR; + le->pos.trTime = cg.time; + // we better not ever have a spawner with a velocity that we expect to last forever, so just plain + //assigning killTime here _should_ be ok + le->pos.trDuration = killTime; + +// if (elasticity > 0) +// { +// le->leFlags |= LEF_USE_COLLISION; +// le->bounceFactor = elasticity; +// } + } + + return (le); +} + +// provide the center of the circle, a normal out from it (normalized, please), and the radius. +//out will then become a random position on the radius of the circle. +void fxRandCircumferencePos(vec3_t center, vec3_t normal, float radius, vec3_t out) +{ + float rnd = flrandom(0, 2*M_PI); + float s = sin(rnd); + float c = cos(rnd); + vec3_t vTemp, radialX, radialY; + + vTemp[0]=0.57735; + vTemp[1]=0.57735; + vTemp[2]=0.57735; + CrossProduct(normal, vTemp, radialX); + CrossProduct(normal, radialX, radialY); + VectorScale(radialX, radius, radialX); + VectorScale(radialY, radius, radialY); + VectorMA(center, s, radialX, out); + VectorMA(out, c, radialY, out); +} diff --git a/cgame/fx_local.h b/cgame/fx_local.h new file mode 100644 index 0000000..f6c40c3 --- /dev/null +++ b/cgame/fx_local.h @@ -0,0 +1,197 @@ + +#define DEFAULT_DEVIATION 0.5 + +// +// fx_*.c +// + +void FXE_Spray (vec3_t direction, float speed, float variation, float cone, vec3_t velocity); +localEntity_t *FX_AddLine(vec3_t start, vec3_t end, float stScale, float scale, float dscale, + float startalpha, float endalpha, float killTime, qhandle_t shader); +localEntity_t *FX_AddLine2(vec3_t start, vec3_t end, float stScale, float width1, float dwidth1, float width2, float dwidth2, + float startalpha, float endalpha, vec3_t startRGB, vec3_t endRGB, float killTime, qhandle_t shader); +localEntity_t *FX_AddOrientedLine(vec3_t start, vec3_t end, vec3_t normal, float stScale, float scale, + float dscale, float startalpha, float endalpha, float killTime, qhandle_t shader); +localEntity_t *FX_AddTrail( vec3_t origin, vec3_t velocity, qboolean gravity, float length, float dlength, + float scale, float dscale, float startalpha, float endalpha, + float elasticity, float killTime, qhandle_t shader); +localEntity_t *FX_AddTrail2( vec3_t origin, vec3_t velocity, qboolean gravity, float length, float dlength, + float scale, float dscale, float startalpha, float endalpha, vec3_t startRGB, vec3_t endRGB, + float elasticity, float killTime, qhandle_t shader); +localEntity_t *FX_AddSprite(vec3_t origin, vec3_t velocity, qboolean gravity, float scale, float dscale, + float startalpha, float endalpha, float roll, float elasticity, + float killTime, qhandle_t shader); +localEntity_t *FX_AddSprite2(vec3_t origin, vec3_t velocity, qboolean gravity, float scale, float dscale, + float startalpha, float endalpha, vec3_t startRGB, vec3_t endRGB, float roll, float elasticity, + float killTime, qhandle_t shader); +localEntity_t *FX_AddBezier(vec3_t start, vec3_t end, vec3_t cpoint1, vec3_t cpoint2, vec3_t cpointvel1, + vec3_t cpointvel2,vec3_t cpointacc1, vec3_t cpointacc2, float width, + float killTime, qhandle_t shader); +localEntity_t *FX_AddQuad( vec3_t origin, vec3_t normal, float scale, float dscale, + float startalpha, float endalpha, float roll, float killTime, qhandle_t shader ); +localEntity_t *FX_AddQuad2( vec3_t origin, vec3_t normal, float scale, float dscale, float startalpha, float endalpha, + vec3_t startRGB, vec3_t endRGB, float roll, float killTime, qhandle_t shader ); +localEntity_t *FX_AddCylinder( vec3_t start, + vec3_t normal, + float height, + float dheight, + float scale, + float dscale, + float scale2, + float dscale2, + float startalpha, + float endalpha, + float killTime, + qhandle_t shader, + float bias ); +localEntity_t *FX_AddElectricity( vec3_t origin, vec3_t origin2, float stScale, float scale, float dscale, + float startalpha, float endalpha, float killTime, qhandle_t shader, float deviation ); +localEntity_t *FX_AddParticle( vec3_t origin, vec3_t velocity, qboolean gravity, float scale, float dscale, + float startalpha, float endalpha, float roll, float elasticity, + float killTime, qhandle_t shader, qboolean (*thinkFn)(localEntity_t *le)); + +localEntity_t *FX_AddSpawner( vec3_t origin, vec3_t dir, vec3_t velocity, vec3_t user, qboolean gravity, int delay, + float variance, float killTime, qboolean (*thinkFn)(localEntity_t *le), int radius ); + +// +// phaser +// + +void FX_PhaserFire( vec3_t start, vec3_t end, vec3_t normal, qboolean spark, qboolean impact, qboolean empty ); +void FX_PhaserAltFire( vec3_t start, vec3_t end, vec3_t normal, qboolean spark, qboolean impact, qboolean empty ); + + +// +// compression rifle +// + +void FX_CompressionShot( vec3_t start, vec3_t end ); +void FX_CompressionAltShot( vec3_t start, vec3_t end ); +void FX_CompressionExplosion( vec3_t start, vec3_t origin, vec3_t normal, qboolean altfire ); +void FX_CompressionHit( vec3_t origin ); +//void FX_CompressionHitWall( vec3_t origin, vec3_t dir ); +void FX_PrifleBeamFire( vec3_t startpos, vec3_t endpos, vec3_t normal, qboolean spark, qboolean impact, qboolean empty ); + +void FX_ProbeBeam( vec3_t origin, vec3_t dir, int clientNum, qboolean alt_fire ); +void FX_RegenBeam( vec3_t origin, vec3_t dir, int clientNum, qboolean alt_fire ); + +// +// imod +// + +//void FX_IMODShot( vec3_t end, vec3_t start, vec3_t dir); +//void FX_IMODExplosion( vec3_t origin, vec3_t normal ); +//void FX_AltIMODShot( vec3_t end, vec3_t start, vec3_t dir ); +//void FX_AltIMODExplosion( vec3_t origin, vec3_t normal ); +// +// tetrion disruptor +// +//void FX_TetrionProjectileThink( centity_t *cent, const struct weaponInfo_s *wi ); +void FX_TetrionShot( vec3_t start, vec3_t forward ); +void FX_TetrionWeaponHitWall( vec3_t origin, vec3_t normal ); +//void FX_TetrionRicochet( vec3_t origin, vec3_t normal ); +//void FX_TetrionAltHitWall( vec3_t origin, vec3_t normal ); +void FX_TetrionAltHitPlayer( vec3_t origin, vec3_t normal ); +// +// Scavenger Rifle +// +void FX_HypoSpray( vec3_t origin, vec3_t dir, qboolean red ); +//void FX_ScavengerProjectileThink( centity_t *ent, const weaponInfo_t *wi ); +//void FX_ScavengerAltFireThink( centity_t *ent, const weaponInfo_t *wi ); +//void FX_ScavengerWeaponHitWall( vec3_t origin, vec3_t normal, qboolean fired_by_NPC ); +//void FX_ScavengerWeaponHitPlayer( vec3_t origin, vec3_t normal, qboolean fired_by_NPC ); +//void FX_ScavengerAltExplode( vec3_t origin, vec3_t dir ); +// +// Grenade launcher +// +void FX_GrenadeThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_GrenadeHitWall( vec3_t origin, vec3_t normal ); +void FX_GrenadeHitPlayer( vec3_t origin, vec3_t normal ); +void FX_GrenadeExplode( vec3_t origin, vec3_t normal ); +void FX_GrenadeShrapnelExplode( vec3_t origin, vec3_t norm ); +void FX_GrenadeShrapnelBits( vec3_t start); +void FX_fxfunc_Explosion( vec3_t start, vec3_t origin, vec3_t normal ); +void FX_fxfunc_Shot( vec3_t start, vec3_t dir ); + +// Borg FX +//void FX_BorgProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +//void FX_BorgWeaponHitWall( vec3_t origin, vec3_t normal ); +//void FX_BorgTaser( vec3_t start, vec3_t end ); +//void FX_BorgEyeBeam( vec3_t start, vec3_t end, vec3_t normal, qboolean large ); +//void FX_BorgTeleport( vec3_t origin ); +//void FX_BorgTeleportTrails( vec3_t origin );// effect seen by other borg when you are in a mid-teleport + +// +// detpack +// + +void FX_Detpack(vec3_t origin); + + +// +// Stasis Weapon +// +//Disruptor +void FX_DisruptorBeamFire( vec3_t startpos, vec3_t endpos, vec3_t normal, qboolean spark, qboolean impact, qboolean empty ); + + +//void FX_StasisProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_StasisWeaponHitWall( vec3_t origin, vec3_t dir, int size ); +//void FX_StasisWeaponHitPlayer( vec3_t origin, vec3_t dir, int size ); +//void FX_StasisShot( centity_t *cent, vec3_t end, vec3_t start ); +//void FX_StasisShotImpact( vec3_t end, vec3_t dir ); +//void FX_StasisShotMiss( vec3_t end, vec3_t dir ); + +// +// Quantum Burst +// + +void FX_QuantumThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_QuantumAltThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_QuantumHitWall( vec3_t origin, vec3_t normal ); +void FX_QuantumAltHitWall( vec3_t origin, vec3_t normal ); +void FX_QuantumColumns( vec3_t origin ); + + +// +// Dreadnought +// + +//void FX_DreadnoughtHitWall( vec3_t origin, vec3_t normal, qboolean spark ); +//void FX_DreadnoughtFire( vec3_t origin, vec3_t end, vec3_t normal, qboolean spark, qboolean impact ); +//void FX_DreadnoughtProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +//void FX_DreadnoughtShotMiss( vec3_t end, vec3_t dir ); + + +// +// transporter +// + +void FX_Transporter(vec3_t origin); +void FX_TransporterPad( vec3_t origin ); +void FX_SPTransporterLensFlares( centity_t* cent, vec3_t headVector, int startTime ); + + +// Holdable, portable shield item +void FX_DrawPortableShield(centity_t *cent); + + + +// Shield +void FX_PlayerShieldHit( centity_t *cent ); + + +// +// Miscellaneous FX +// + +void FX_Disruptor( vec3_t org, float length ); +void FX_ExplodeBits( vec3_t org); + +void FX_qFlash( centity_t* cent, vec3_t org, int timeIndex ); + +// +// sin table +// + +void fxRandCircumferencePos(vec3_t center, vec3_t normal, float radius, vec3_t out); diff --git a/cgame/fx_misc.c b/cgame/fx_misc.c new file mode 100644 index 0000000..6b26afc --- /dev/null +++ b/cgame/fx_misc.c @@ -0,0 +1,286 @@ +#include "cg_local.h" +#include "fx_local.h" + + +/* +------------------------- +FX_Disruptor +------------------------- +*/ +void DisruptorShards(vec3_t org) +{ + vec3_t normal, end; + + // Pick a random endpoint + VectorSet( normal, crandom(), crandom(), crandom() ); + VectorNormalize( normal ); + + end[0] = org[0] + ( normal[0] * ( 48 + crandom() * 16 )); + end[1] = org[1] + ( normal[1] * ( 48 + crandom() * 16 )); + end[2] = org[2] + ( normal[2] * ( 64 + crandom() * 24 )); + + // Draw a light shard, use a couple of different kinds so it doesn't look too homogeneous + if( rand() & 1 ) + { + FX_AddLine( org, end, 1.0, random() * 0.5 + 0.5, 12.0, random() * 0.1 + 0.1, 0.0, 200 + random() * 350, cgs.media.orangeParticleShader ); + } + else + { + FX_AddLine( org, end, 1.0, random() * 0.5 + 0.5, 12.0, random() * 0.1 + 0.1, 0.0, 200 + random() * 350, cgs.media.yellowParticleShader ); + } +} + +qboolean MakeDisruptorShard( localEntity_t *le ) +{ + DisruptorShards(le->refEntity.origin); + return(qtrue); +} + +// Effect used when scav beams in--this wouldn't work well for a scav on the ground if they were to beam out +void FX_Disruptor( vec3_t org, float length ) +{//FIXME: make it move with owner? + vec3_t org1, org2, normal={0,0,1}; + int t; + + VectorMA( org, 48, normal, org1 ); + VectorMA( org, -48, normal, org2 ); + + // This is the core + FX_AddLine( org1, org2, 1.0, 0.1, 48.0, 1.0, 0.0, length, cgs.media.dkorangeParticleShader ); + + // Spawn a bunch to get the effect going + for (t=0; t < 12; t++ ) + { + DisruptorShards( org); + } + + // Keep spawning the light shards for a while. + FX_AddSpawner( org, normal, NULL, NULL, qfalse, 20, 10, length*0.75, MakeDisruptorShard, 0); +} + + + + +void FX_EnergyGibs(vec3_t origin ) +{ + localEntity_t *le; + refEntity_t *re; + vec3_t dir; + int i, j, k; + int chunkModel=0; + float baseScale = 0.7f, dist; + int numChunks; + + numChunks = irandom( 10, 15 ); + + VectorSubtract(cg.snap->ps.origin, origin, dir); + dist = VectorLength(dir); + if (dist > 512) + { + numChunks *= 512.0/dist; // 1/2 at 1024, 1/4 at 2048, etc. + } + + for ( i = 0; i < numChunks; i++ ) + { + chunkModel = cgs.media.chunkModels[MT_METAL][irandom(0,5)]; + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FRAGMENT; + le->endTime = cg.time + 2000; + + VectorCopy( origin, re->origin ); + + for ( j = 0; j < 3; j++ ) + { + re->origin[j] += crandom() * 12; + } + VectorCopy( re->origin, le->pos.trBase ); + + //Velocity + VectorSet( dir, crandom(), crandom(), crandom() ); + VectorScale( dir, flrandom( 300, 500 ), le->pos.trDelta ); + + //Angular Velocity + VectorSet( le->angles.trBase, crandom() * 360, crandom() * 360, crandom() * 360 ); + VectorSet( le->angles.trDelta, crandom() * 90, crandom() * 90, crandom() * 90 ); + + AxisCopy( axisDefault, re->axis ); + + le->data.fragment.radius = flrandom(baseScale * 0.4f, baseScale * 0.8f ); + + re->nonNormalizedAxes = qtrue; + re->hModel = chunkModel; + re->renderfx |= RF_CAP_FRAMES; + re->customShader = cgs.media.quantumDisruptorShader; + re->shaderTime = cg.time/1000.0f; + + le->pos.trType = TR_GRAVITY; + le->pos.trTime = cg.time; + le->angles.trType = TR_INTERPOLATE; + le->angles.trTime = cg.time; + le->bounceFactor = 0.2f + random() * 0.2f; + le->leFlags |= LEF_TUMBLE; + + re->shaderRGBA[0] = re->shaderRGBA[1] = re->shaderRGBA[2] = re->shaderRGBA[3] = 255; + + // Make sure that we have the desired start size set + for( k = 0; k < 3; k++) + { + VectorScale(le->refEntity.axis[k], le->data.fragment.radius, le->refEntity.axis[k]); + } + } +} + + + +void FX_ExplodeBits( vec3_t org) +{ + float width, length; + vec3_t vel, pos; + int i; + + FX_EnergyGibs(org); + + for (i = 0; i < 32; i++) + { + VectorSet(vel, flrandom(-320,320), flrandom(-320,320), flrandom(-100,320)); + VectorCopy(org, pos); + pos[2] += flrandom(-8, 8); + length = flrandom(10,20); + width = flrandom(2.0,4.0); + FX_AddTrail( pos, vel, qtrue, length, -length, width, -width, + 1.0f, 1.0f, 0.5f, 1000.0f, cgs.media.orangeTrailShader); + } +} + +#define Q_FLASH_SIZE 110 + +void FX_qFlash( centity_t* cent, vec3_t org, int timeIndex ) { + trace_t tr; + refEntity_t flare; + float frac; + + if ( cg.predictedPlayerState.clientNum != cent->currentState.clientNum ) { + CG_Trace( &tr, cg.refdef.vieworg, NULL, NULL, + cent->lerpOrigin, cg.predictedPlayerState.clientNum, CONTENTS_SOLID ); + if ( tr.fraction != 1 ) { + return; + } + } + + memset( &flare, 0, sizeof( flare ) ); + + flare.reType = RT_SPRITE; + flare.shaderRGBA[0] = 0xff; + flare.shaderRGBA[1] = 0xff; + flare.shaderRGBA[2] = 0xff; + flare.shaderRGBA[3] = 0xff; + + flare.data.sprite.rotation = 0; + flare.nonNormalizedAxes = qtrue; //needed for effective scaling + + flare.customShader = cgs.media.qFlashSprite; + + flare.renderfx |= RF_DEPTHHACK; + + VectorCopy( org, flare.origin ); + + //find the basic ratio + frac = (float)(cg.time - timeIndex) / (float)( Q_FLASH_TIME ); + //apply a sine function to it to make it less linear + //calculated using the fine graph prog @ http://math.umn.edu/~garrett/a08/Graph.html + frac = ( 0.65f * sin( 4.5f * frac - 0.6f ) + 0.35f ); + + frac = Com_Clamp( 0.0f, 1.0f, frac ); + + //CG_Printf( "%f\n", frac ); + + flare.data.sprite.radius = (float)Q_FLASH_SIZE * frac; + + trap_R_AddRefEntityToScene( &flare ); +} + +#define PROBE_BEAM_LENGTH 32 +//TiM - Beam FX for the Neutrino Probe weapon +void FX_ProbeBeam( vec3_t origin, vec3_t dir, int clientNum, qboolean alt_fire ) +{ + trace_t tr; + refEntity_t beam; + vec3_t end; + float scale; + + memset( &beam, 0, sizeof( beam ) ); + + if ( alt_fire ) + scale = flrandom(7.0f, 12.0f); + else + scale = Q_fabs( 12.0f * sin( cg.time * 0.1f ) ); + + VectorMA( origin, PROBE_BEAM_LENGTH, dir, end ); + + CG_Trace( &tr, origin, NULL, NULL, end, clientNum, CONTENTS_SOLID ); + + trap_R_AddLightToScene( origin, 20, 114.0f / 255, 164.0f / 255, 1.0f ); + + VectorCopy( origin, beam.origin); + VectorCopy( tr.endpos, beam.oldorigin ); + beam.reType = RT_LINE; + beam.customShader = cgs.media.probeBeam; + beam.shaderRGBA[0] = 0xff; + beam.shaderRGBA[1] = 0xff; + beam.shaderRGBA[2] = 0xff; + beam.shaderRGBA[3] = 0xff; + AxisClear( beam.axis ); + + beam.data.line.width = scale*0.1; + beam.data.line.width2 = scale; + beam.data.line.stscale = 1.0; + trap_R_AddRefEntityToScene( &beam ); + + if ( tr.fraction != 1.0f ) + { + float radius; + + if ( alt_fire ) + radius = flrandom(1.5f, 3.0f) * (1.0 - (tr.fraction*0.3)); + else + radius = flrandom(0.5f, 1.5f) * (1.0 - (tr.fraction*0.3)); + + if ( !radius ) + return; + + CG_ImpactMark( cgs.media.probeDecal, tr.endpos, tr.plane.normal, 0, 1, 1, 1, 0.2*(1.0-tr.fraction), qfalse, radius, qtrue ); + trap_R_AddLightToScene( origin, radius*5, 114.0f / 255, 164.0f / 255, 1.0f ); + } +} + +#define REGEN_BEAM_LENGTH 64 +void FX_RegenBeam( vec3_t origin, vec3_t dir, int clientNum, qboolean alt_fire ) +{ + trace_t tr; + vec3_t end; + + VectorMA( origin, REGEN_BEAM_LENGTH, dir, end ); + + CG_Trace( &tr, origin, NULL, NULL, end, clientNum, CONTENTS_SOLID ); + + trap_R_AddLightToScene( origin, 30, 235.0f / 255, 74.0f / 255, 102.0f / 255 ); + + if ( tr.fraction != 1.0f ) + { + float radius; + + if ( alt_fire ) + radius = flrandom(1.5f, 3.0f) * (1.0 - (tr.fraction*0.3)); + else + radius = flrandom(0.5f, 1.5f) * (1.0 - (tr.fraction*0.3)); + + if ( !radius ) + return; + + CG_ImpactMark( cgs.media.regenDecal, tr.endpos, tr.plane.normal, 0, 1, 1, 1, 0.2*(1.0-tr.fraction), qfalse, radius, qtrue ); + trap_R_AddLightToScene( origin, radius*5, 235.0f / 255, 74.0f / 255, 102.0f / 255 ); + } +} diff --git a/cgame/fx_phaser.c b/cgame/fx_phaser.c new file mode 100644 index 0000000..a8b94e0 --- /dev/null +++ b/cgame/fx_phaser.c @@ -0,0 +1,357 @@ +//Phaser + +#include "cg_local.h" +#include "fx_local.h" + +/* +------------------------- +FX_PhaserFire +------------------------- +*/ + +lensFlare_t phaserFlare = { {0.0,0.0,0.0}, + 20, + 20, + {1.0, 0.7, 0.13}, + 1.2, + 1.5, + 20, + 300, + {0.0, 0.0, 0.0}, + 20, + 300, + 80, + 5, + qfalse, + 5, + 40, + qfalse, + qfalse, + qtrue, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + qtrue }; + +void FX_PhaserFire( vec3_t startpos, vec3_t endpos, vec3_t normal, qboolean spark, qboolean impact, qboolean empty ) +{ + refEntity_t beam; + sfxHandle_t sfx; + float size; + vec3_t velocity; + int sparks; + vec3_t rgb = { 1,0.9,0.6}, rgb2={1,0.3,0}; + + //vec3_t rgb3 = { 1.0, 1.0, 1.0 }; + + sfx = 0; + + // Draw beam first. + memset( &beam, 0, sizeof( beam ) ); + + VectorCopy( startpos, beam.origin); + VectorCopy( endpos, beam.oldorigin ); + beam.reType = RT_LINE; + if (empty) + { + beam.customShader = cgs.media.phaserEmptyShader; + } + else + { + beam.customShader = cgs.media.phaserShader; + } + AxisClear( beam.axis ); + beam.shaderRGBA[0] = 0xff; + beam.shaderRGBA[1] = 0xff; + beam.shaderRGBA[2] = 0xff; + beam.shaderRGBA[3] = 0xff; + if (empty) + { + beam.data.line.width = 1.0f + ( crandom() * 0.6f ); + } + else + { + beam.data.line.width = 2.0f + ( crandom() * 0.6f ); + } + beam.data.line.stscale = 5.0; + trap_R_AddRefEntityToScene( &beam ); + + // Now draw the hit graphic + + // no explosion at LG impact, it is added with the beam + + if ( sfx ) + { + Com_Printf("playing %s\n", "phaser sound"); + trap_S_StartSound( endpos, ENTITYNUM_WORLD, CHAN_AUTO, sfx ); + } + + // + // impact mark + // + if (impact) + { + if (!empty) + { // normal. + CG_ImpactMark( cgs.media.scavMarkShader, endpos, normal, random()*360, 1,1,1,0.2, qfalse, + random() + 1, qfalse ); + + //VectorCopy( endpos, phaserFlare.worldCoord ); + + /*CG_InitLensFlare( endpos, + 80, + 80, + rgb, + 1.2, + 1.5, + 1600, + 200, + colorTable[CT_BLACK], + 1600, + 200, + 80, + 5, + qfalse, + 5, + 40, + qfalse, + qfalse, + qfalse, + 1.0, + 1.0, + 200.0, + 200.0, + 200.0 );*/ + + //CG_InitLensFlare( endpos, + // 30, 30, + // rgb, 1.2, 2.0, 1600, 200, + // colorTable[CT_BLACK], 1600, 200, 410, 15, qfalse, + // 0, 0, qfalse, qtrue, + // qfalse, 1.0, cg.time, 0, 0, 50); + + //TiM : Add your basic cheesy 'seen-way-too-much-in-movies-these-days' anamorphic lens streak :) + //CG_DrawLensFlare( &phaserFlare ); + //FX_AddSprite( endpos, NULL, qfalse, random() * 1.25 + 5.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 50.0, cgs.media.blueParticleStreakShader ); //1.5f + + //FX_AddQuad2( endpos, normal, random() * 1.25 + 8.0f, 0.0f, 1.0f, 1.0f, rgb3, rgb3, 270, 50.0, cgs.media.blueParticleStreakShader ); + //eh... looked bad :P + + FX_AddQuad2( endpos, normal, random() * 1.25 + 1.5f, 0.0f, 1.0f, 0.0f, rgb, rgb2, rand() % 360, 500 + random() * 200, + cgs.media.sunnyFlareShader ); + } + else + { // Wuss hit when empty. + FX_AddQuad2( endpos, normal, random() * .75 + 1.0f, 0.0f, 0.5f, 0.0f, rgb, rgb2, rand() % 360, 300 + random() * 200, + cgs.media.sunnyFlareShader ); + } + } + + // "Fun" sparks... Not when empty. + if ( spark && !empty) + { + sparks = rand() & 1 + 1; + for(;sparks>0;sparks--) + { + size = 0.2f + (random() * 0.4); + FXE_Spray( normal, 200, 75, 0.8f, velocity); + if (rand() & LEF_USE_COLLISION) + { // This spark bounces. + FX_AddTrail( endpos, velocity, qtrue, 5.0f, -15.0f, + size, -size, 1.0f, 0.5f, 0.4f, 500.0f, cgs.media.sparkShader); + } + else + { + FX_AddTrail( endpos, velocity, qtrue, 5.0f, -15.0f, + size, -size, 1.0f, 0.5f, 0.0, 500.0f, cgs.media.sparkShader); + } + } + } +} + +/* +------------------------- +FX_PhaserAltFire +------------------------- +*/ + +#define PHASER_ALT_CONE_LEN 256 + +void FX_PhaserAltFire( vec3_t start, vec3_t end, vec3_t normal, qboolean spark, qboolean impact, qboolean empty ) +{ + float scale = flrandom(13.0f, 17.0f), scale2 = flrandom(2.0f, 6.0f); + vec3_t vel, diff, end2; + int i = 0, sparks = 0; + refEntity_t beam; + vec3_t rgb = { 1,0.6,0.5}, rgb2={1,0.3,0}; + float len; + int color; + + VectorSubtract(end, start, diff); + len = VectorNormalize(diff); + + color = 0xff * flrandom(0.75, 1.0); + + if (empty) + { // More faint and shaky line. + scale *= flrandom(0.25,0.75); + } + + if (len > PHASER_ALT_CONE_LEN) + { // Draw beam in two parts... + + // Draw main beam first. + VectorMA(start, PHASER_ALT_CONE_LEN, diff, end2); + + // Draw starting cone + memset( &beam, 0, sizeof( beam ) ); + VectorCopy( start, beam.origin); + VectorCopy( end2, beam.oldorigin ); + beam.reType = RT_LINE2; + if (empty) + { + beam.customShader = cgs.media.phaserAltEmptyShader; + } + else + { + beam.customShader = cgs.media.phaserAltShader; + } + AxisClear( beam.axis ); + beam.shaderRGBA[0] = 0xff; + beam.shaderRGBA[1] = 0xff*0.3; + beam.shaderRGBA[2] = 0; + beam.shaderRGBA[3] = 0xff; + beam.data.line.width = scale*0.1; + beam.data.line.width2 = scale; + beam.data.line.stscale = 1.0; + trap_R_AddRefEntityToScene( &beam ); + + // Draw big thick normal beam for the rest. + memset( &beam, 0, sizeof( beam ) ); + VectorCopy( end2, beam.oldorigin); + VectorCopy( end, beam.origin ); + beam.reType = RT_LINE; + if (empty) + { + beam.customShader = cgs.media.phaserAltEmptyShader; + } + else + { + beam.customShader = cgs.media.phaserAltShader; + } + AxisClear( beam.axis ); + beam.shaderRGBA[0] = 0xff; + beam.shaderRGBA[1] = 0xff*0.3; + beam.shaderRGBA[2] = 0; + beam.shaderRGBA[3] = 0xff; + beam.data.line.width = scale; + beam.data.line.stscale = 1.0; + trap_R_AddRefEntityToScene( &beam ); + + // Draw beam core, all one bit. + memset( &beam, 0, sizeof( beam ) ); + VectorCopy( start, beam.origin); + VectorCopy( end, beam.oldorigin ); + beam.reType = RT_LINE2; + beam.customShader = cgs.media.phaserShader; + AxisClear( beam.axis ); + beam.shaderRGBA[0] = color*0.75f; + beam.shaderRGBA[1] = 0xff*0.5f; + beam.shaderRGBA[2] = 0xff*0.5f; + beam.shaderRGBA[3] = 0xff; + beam.data.line.width = scale2*0.2; + beam.data.line.width2 = scale2; + beam.data.line.stscale = 1.0; + trap_R_AddRefEntityToScene( &beam ); + } + else + { // Draw beam in two parts... + // Draw beam first. + memset( &beam, 0, sizeof( beam ) ); + VectorCopy( start, beam.origin); + VectorCopy( end, beam.oldorigin ); + beam.reType = RT_LINE2; + beam.customShader = cgs.media.phaserAltShader; + AxisClear( beam.axis ); + beam.shaderRGBA[0] = 0xff; + beam.shaderRGBA[1] = 0xff*0.3; + beam.shaderRGBA[2] = 0; + beam.shaderRGBA[3] = 0xff; + beam.data.line.width = scale*0.1; + beam.data.line.width2 = scale; + beam.data.line.stscale = 1.0; + trap_R_AddRefEntityToScene( &beam ); + + // just one beam is never enough + memset( &beam, 0, sizeof( beam ) ); + VectorCopy( start, beam.origin); + VectorCopy( end, beam.oldorigin ); + beam.reType = RT_LINE2; + beam.customShader = cgs.media.phaserShader; + AxisClear( beam.axis ); + beam.shaderRGBA[0] = color*0.75f; + beam.shaderRGBA[1] = 0xff*0.5f; + beam.shaderRGBA[2] = 0xff*0.5f; + beam.shaderRGBA[3] = 0xff; + beam.data.line.width = scale2*0.2; + beam.data.line.width2 = scale2; + beam.data.line.stscale = 1.0; + trap_R_AddRefEntityToScene( &beam ); + } + + + // Phaser beam +// FX_AddLine( start, end, 1.0f, scale, 0.0f, 0.9f, 0.9f, 2, cgs.media.phaserShader ); +// FX_AddLine( start, end, 1.0f, scale * 0.5f, 0.0f, 0.8f, 0.8f, 2, cgs.media.phaserShader ); + + // Per frame impact mark + FX_AddQuad( end, normal, random() * 1.5 + 1.75f, 0.0f, 1.0f, 0.0f, 0.0f, 1, cgs.media.sparkShader ); + FX_AddQuad( end, normal, random() * 5 + 2.75f, 0.0f, 1.0f, 0.0f, 0.0f, 1, cgs.media.yellowParticleShader ); + + // Multi frame impacts--never do this when it hits a player because it will just look stupid + if ( impact ) + { + FX_AddQuad2( end, normal, random() * 2.0 + 5.0f, 2.5f, 0.6f, 0.0f, rgb, rgb2, 0.0f, 500 + random() * 200, + cgs.media.sunnyFlareShader ); + + CG_ImpactMark( cgs.media.scavMarkShader, end, normal, random()*360, 1,1,1,0.1, qfalse, + random() + 6.0, qfalse ); + } + + // "Fun" sparks + if ( spark ) + { + // kef -- fixme. dunno what the deal is with this velocity vector + VectorClear(vel); + sparks = rand() & 3 + 1; + + // Set random starting pos... + end2[0] = flrandom(-1.0, 1.0) + end[0]; + end2[1] = flrandom(-1.0, 1.0) + end[1]; + end2[2] = flrandom(-1.0, 1.0) + end[2]; + for( i = 0; i < sparks; i++ ) + { + scale = 0.5f + (random() * 0.5); + FXE_Spray( normal, 200, 75, 0.8f, /*1024*/vel); + FX_AddTrail2( end2, vel, qfalse, + 8.0f, -8.0f, + scale, -scale, 0.5f, 0.0f, rgb, rgb2, 0.4f, 500.0f, cgs.media.sparkShader ); + } + + VectorMA(end, -8, diff, end2); + // Add a hit sprite over everything... + memset( &beam, 0, sizeof( beam ) ); + VectorCopy( end2, beam.origin); + beam.reType = RT_SPRITE; + beam.customShader = cgs.media.sunnyFlareShader; + AxisClear( beam.axis ); + beam.shaderRGBA[0] = 0xff*1.0f; + beam.shaderRGBA[1] = 0xff*0.9f; + beam.shaderRGBA[2] = 0xff*0.8f; + beam.shaderRGBA[3] = 0xff; + beam.data.sprite.radius = random()*2.0 + 9.0; + trap_R_AddRefEntityToScene( &beam ); + } +} diff --git a/cgame/fx_quantum.c b/cgame/fx_quantum.c new file mode 100644 index 0000000..1979153 --- /dev/null +++ b/cgame/fx_quantum.c @@ -0,0 +1,415 @@ +#include "cg_local.h" +#include "fx_local.h" + + +// think function for the quantum explosion particles +//unused +/*qboolean explosionTailThink(localEntity_t *le) +{ + refEntity_t *re = &le->refEntity; + float length = 20; + + // leave a cool tail + CG_AddTrail(FX_AddTrail( re->origin, + le->data.particle.dir, qtrue, + length, 0, + le->data.particle.radius*0.8, le->data.particle.dradius, + 0.2, 0.0, // alpha, dalpha + 0, + 1, + cgs.media.yellowParticleShader )); + + return qtrue; +}*/ + +/* +------------------------- +FX_QuantumThink +------------------------- +*/ + + /*vec3_t worldCoord; + int w1; + int h1; + vec3_t glowColor; + float glowOffset; + float hazeOffset; + int minDist; + int maxDist; + vec3_t streakColor; + int streakDistMin; + int streakDistMax; + int streakW; + int streakH; + qboolean whiteStreaks; + int reflecDistMin; + int reflecDistMax; + qboolean reflecAnamorphic; + qboolean defReflecs; + qboolean clamp; + float maxAlpha; + int startTime; + int upTime; + int holdTime; + int downTime; + qboolean qfull; + +static lensFlare_t quantumFlare = { {0.0, 0.0, 0.0}, 100, 100, // worldCoord, w1, h1 + {0.0, 1.0, 1.0}, 1.1, 1.3, 1000, 40,//glowColor, glowOffset, hazeOffset, minDist, maxDist + {0.0, 1.0, 1.0}, 1000, 40, 600, 10, qtrue, //streakColor, sDistMin, sDistMax, sW, sH, whitestreaks + 0, 1, qfalse, qfalse, qfalse, //rDistMin, rDistMax, rAna, rDef, clamp + 1.0, 0, 0, 0, 0, qtrue }; //maxalpha, start, up, hold, down, full */ + +void FX_QuantumThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t line1end, line2end, axis[3], rgb; + + AxisClear( axis ); + + // convert direction of travel into axis + if ( VectorNormalize2( cent->currentState.pos.trDelta, axis[0] ) == 0 ) { + axis[0][2] = 1; + } + + // spin as it moves + RotateAroundDirection( axis, cg.time * 0.3f );// * 1.25f ); + + VectorMA( cent->lerpOrigin, -24.0f, axis[1], line1end ); + VectorMA( cent->lerpOrigin, 24.0f, axis[1], line2end ); + FX_AddLine( line1end, line2end, 1.0f, random() * 6 + 2, 0.0f, 0.2 + random() * 0.2, 0.0f, 1, cgs.media.yellowParticleShader ); + + AxisClear( axis ); + + // convert direction of travel into axis + if ( VectorNormalize2( cent->currentState.pos.trDelta, axis[0] ) == 0 ) { + axis[0][2] = 1; + } + + // spin as it moves + RotateAroundDirection( axis, -cg.time * 0.3f );// * 1.25f ); + + VectorMA( cent->lerpOrigin, -48.0f, axis[2], line1end ); + VectorMA( cent->lerpOrigin, 48.0f, axis[2], line2end ); + FX_AddLine( line1end, line2end, 1.0f, random() * 5 + 2, 0.0f, 0.1 + random() * 0.2, 0.0f, 1, cgs.media.yellowParticleShader ); + + VectorSet( rgb, 1.0f, 0.45f, 0.15f ); // orange + + FX_AddSprite( cent->lerpOrigin, NULL,qfalse,random() * 60 + 30, 4, 0.5f, 0.0f, 0, 0.0f, 1.0f, cgs.media.orangeParticleShader ); + FX_AddSprite2(cent->lerpOrigin, NULL,qfalse,random() * 10 + 60, 0.0f, 0.1f, 0.1f, rgb, rgb, 0.0f, 0.0f, 1, cgs.media.whiteRingShader ); + FX_AddSprite( cent->lerpOrigin, NULL,qfalse,random() * 16 + 8, 4, 0.5f, 0.0f, 0, 0.0f, 1.0f, cgs.media.yellowParticleShader ); + + /* + VectorCopy( cent->lerpOrigin, quantumFlare.worldCoord ); + VectorCopy( colorTable[CT_CYAN], quantumFlare.glowColor ); + VectorCopy( colorTable[CT_CYAN], quantumFlare.streakColor ); + + CG_DrawLensFlare( quantumFlare );*/ +} + +/* +------------------------- +FX_QuantumAltThink +------------------------- +*/ +void FX_QuantumAltThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t line1end, line2end, axis[3], vel, rgb; + float scale; + + AxisClear( axis ); + + // convert direction of travel into axis + if ( VectorNormalize2( cent->currentState.pos.trDelta, axis[0] ) == 0 ) { + axis[0][2] = 1; + } + + // spin as it moves + RotateAroundDirection( axis, cg.time * 0.3f );// * 1.25f ); + + VectorMA( cent->lerpOrigin, -48.0f, axis[1], line1end ); + VectorMA( cent->lerpOrigin, 48.0f, axis[1], line2end ); + FX_AddLine( line1end, line2end, 1.0f, random() * 6 + 2, 0.0f, 0.2 + random() * 0.2, 0.0f, 1, cgs.media.yellowParticleShader ); + + VectorMA( cent->lerpOrigin, -48.0f, axis[2], line1end ); + VectorMA( cent->lerpOrigin, 48.0f, axis[2], line2end ); + FX_AddLine( line1end, line2end, 1.0f, random() * 5 + 2, 0.0f, 0.2 + random() * 0.2, 0.0f, 1, cgs.media.yellowParticleShader ); + + VectorSet( rgb, 1.0f, 0.45f, 0.15f ); // orange + + FX_AddSprite( cent->lerpOrigin, NULL,qfalse,random() * 60 + 30, 4, 0.5f, 0.0f, 0, 0.0f, 1.0f, cgs.media.orangeParticleShader ); + FX_AddSprite2(cent->lerpOrigin, NULL,qfalse,random() * 10 + 60, 0.0f, 0.1f, 0.1f, rgb, rgb, 0.0f, 0.0f, 1, cgs.media.whiteRingShader ); + FX_AddSprite( cent->lerpOrigin, NULL,qfalse,random() * 16 + 8, 4, 0.5f, 0.0f, 0, 0.0f, 1.0f, cgs.media.yellowParticleShader ); + + scale = ( 2.0f + cos( cg.time * ( M_PI * 0.001f * 4 ))) * 0.5f; + + // Unlike the main fire, I'm leaving around this center core for a moment as a trail... + VectorScale( cent->currentState.pos.trDelta, 0.25f, vel ); + FX_AddSprite( cent->lerpOrigin, NULL,qfalse,scale * 8 + 2, scale * -5.0f, 0.8f, 0.0f, 0, 0, 300.0f, cgs.media.sunnyFlareShader); + + // Tack on a sprite trail so we can see the cool tracking at work. + VectorSet( vel, flrandom(-12, 12), flrandom(-12, 12), flrandom(-12, 12)); + VectorMA( vel, 0.25f, cent->currentState.pos.trDelta, vel); + + if ( rand() & 1 ) + FX_AddSprite( cent->lerpOrigin, vel,qfalse,random() * 12.0f + scale * 14, -10, 0.2f + random() * 0.2f, 0.0, random()*360, 0, 800 + random() * 200.0f, + cgs.media.orangeRingShader ); + else + FX_AddSprite2(cent->lerpOrigin, vel,qfalse,random() * 12.0f + scale * 14, -10, 0.5, 0.0, rgb, rgb, random()*360, 0, 800 + random() * 200.0f, + cgs.media.whiteRingShader ); +} + +/* +------------------------- +FX_QuantumHitWall +------------------------- +*/ +void FX_QuantumHitWall( vec3_t origin, vec3_t normal ) +{ + localEntity_t *le = NULL; + vec3_t dir, org; + vec3_t vel; + float scale; + int i; + weaponInfo_t *weaponInfo = &cg_weapons[WP_QUANTUM_BURST]; + + CG_InitLensFlare( origin, + 400, 400, + colorTable[CT_YELLOW], 1.2, 2.0, 1600, 200, + colorTable[CT_YELLOW], 1600, 200, 800, 35, qtrue, + 0, 0, qfalse, qtrue, + qfalse, 1.0, cg.time, 0, 0, 200); + + for ( i = 0; i < 12; i++ ) + { + VectorSet( dir, normal[0] + crandom() * 2, normal[1] + crandom() * 2, normal[2] + crandom() ); + VectorNormalize( dir ); + scale = random() * 300 + 300; + VectorScale( dir, scale, vel ); + vel[2] += 300; + if ( rand() & 1 ) + { + // FX_AddParticle( origin, vel, qfalse, random() * 14 + 2, -2.0, 0.9, 0.1, 0.0, 0.0, 300 + random() * 100, cgs.media.yellowParticleShader, explosionTailThink ); + scale = random()*14+2; + // Instead of the particle code, which seems redundant and doesn't fade real well, try adding the projectile... + le=FX_AddSprite(origin, vel, qfalse, scale, -scale, 0.9, 0.5, 0.0, 0.0, 200 + random() * 100, cgs.media.yellowParticleShader); + // ...with a trail that overlaps it exactly. + FX_AddTrail(origin, vel, qfalse, 80, -40, scale, -scale, 0.8, 0.4, 0.0, 300, cgs.media.orangeTrailShader); + } + else + { + // FX_AddParticle( origin, vel, qfalse, random() * 14 + 2, -2.0, 0.9, 0.1, 0.0, 0.0, 450 + random() * 200, cgs.media.sunnyFlareShader, explosionTailThink ); + scale = random()*14+6; + // Instead of the particle code, which seems redundant and doesn't fade real well, try adding the projectile... + le=FX_AddSprite(origin, vel, qfalse, scale, -scale, 0.9, 0.5, 0.0, 0.0, 350 + random() * 150, cgs.media.sunnyFlareShader); + // ...with a trail that overlaps it exactly. + FX_AddTrail(origin, vel, qfalse, 80, -40, scale, -scale, 0.8, 0.4, 0.0, 500, cgs.media.orangeTrailShader); + } + } + // Always face the camera + VectorSubtract( cg.refdef.vieworg, origin, dir ); + VectorNormalize( dir ); + + // Main explosion, tag with light + + le = CG_MakeExplosion2( origin, normal, (qhandle_t)0, 1, cgs.media.quantumExplosionShader, 600, qtrue, 3 + crandom(), 0 ); + le->light = 150; + le->refEntity.renderfx |= RF_NOSHADOW; + VectorSet( le->lightColor, 1.0f, 1.0f, 0.6f ); + + // Create sphere + CG_MakeExplosion2( origin, dir, cgs.media.explosionModel, 5, cgs.media.quantumFlashShader, 150, qfalse, 4.6f + ( crandom() * 0.3f), 0 ); + + // Make an offset explosion + for ( i = 0; i < 3; i++ ) { + org[i] = origin[i] + crandom() * 4; + } + + CG_MakeExplosion( org, dir, 0, cgs.media.quantumExplosionShader, 700, 1, qtrue ); + CG_ImpactMark( cgs.media.compressionMarkShader, origin, normal, random()*360, 1,1,1,1.0, qfalse, + random() * 16 + 48, qfalse ); + + CG_ExplosionEffects( origin, 3.0f, 256 ); + + // One big bright quick flash + FX_AddSprite( origin, NULL, qfalse, 100, -100, 1.0, 1.0, 0, 0, 300, cgs.media.sunnyFlareShader); + + + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->mainHitSound ); +} + +/* +------------------------- +FX_QuantumAltHitWall +------------------------- +*/ +void FX_QuantumAltHitWall( vec3_t origin, vec3_t normal ) +{ + localEntity_t *le = NULL; + vec3_t dir, org; + vec3_t vel; + float scale; + int i; + vec3_t RGB={1.0, 0.6, 0.3}, RGB2={1.0, 0.3, 0.0}; + weaponInfo_t *weaponInfo = &cg_weapons[WP_QUANTUM_BURST]; + + CG_InitLensFlare( origin, + 500, 500, + colorTable[CT_YELLOW], 1.2, 2.0, 1600, 200, + colorTable[CT_YELLOW], 1600, 200, 800, 35, qtrue, + 1600, 200, qfalse, qfalse, + qfalse, 1.0, cg.time, 0, 0, 350); + + for ( i = 0; i < 12; i++ ) + { + VectorSet( dir, normal[0] + crandom() * 2, normal[1] + crandom() * 2, normal[2] + crandom() ); + VectorNormalize( dir ); + scale = random() * 500 + 500; + VectorScale( dir, scale, vel ); + vel[2] += 300; + if ( rand() & 1 ) + { + // FX_AddParticle( origin, vel, qfalse, random() * 14 + 2, -2.0, 0.9, 0.1, 0.0, 0.0, 300 + random() * 100, cgs.media.yellowParticleShader, explosionTailThink ); + scale = random()*14+2; + // Instead of the particle code, which seems redundant and doesn't fade real well, try adding the projectile... + le=FX_AddSprite2(origin, vel, qfalse, scale, -scale, 0.9, 0.5, RGB, RGB2, 0.0, 0.0, 200 + random() * 100, cgs.media.yellowParticleShader); + // ...with a trail that overlaps it exactly. + FX_AddTrail2(origin, vel, qfalse, 80, -40, scale, -scale, 0.8, 0.4, RGB, RGB2, 0.0, 300, cgs.media.orangeTrailShader); + } + else + { + // FX_AddParticle( origin, vel, qfalse, random() * 14 + 2, -2.0, 0.9, 0.1, 0.0, 0.0, 450 + random() * 200, cgs.media.sunnyFlareShader, explosionTailThink ); + scale = random()*14+6; + // Instead of the particle code, which seems redundant and doesn't fade real well, try adding the projectile... + le=FX_AddSprite2(origin, vel, qfalse, scale, -scale, 0.9, 0.5, RGB, RGB2, 0.0, 0.0, 350 + random() * 150, cgs.media.sunnyFlareShader); + // ...with a trail that overlaps it exactly. + FX_AddTrail2(origin, vel, qfalse, 80, -40, scale, -scale, 0.8, 0.4, RGB, RGB2, 0.0, 500, cgs.media.orangeTrailShader); + } + } + // Always face the camera + VectorSubtract( cg.refdef.vieworg, origin, dir ); + VectorNormalize( dir ); + + // Main explosion, tag with light + + le = CG_MakeExplosion2( origin, normal, (qhandle_t)0, 1, cgs.media.quantumExplosionShader, 600, qtrue, 3 + crandom(), 0 ); + le->light = 150; + le->refEntity.renderfx |= RF_NOSHADOW; + VectorSet( le->lightColor, 1.0f, 1.0f, 0.6f ); + + // Create sphere + CG_MakeExplosion2( origin, dir, cgs.media.explosionModel, 5, cgs.media.quantumFlashShader, 150, qfalse, 5.4f + ( crandom() * 0.3f), 0 ); + + // Make an offset explosion + for ( i = 0; i < 3; i++ ) { + org[i] = origin[i] + crandom() * 4; + } + + CG_MakeExplosion( org, dir, 0, cgs.media.quantumExplosionShader, 700, 1, qtrue ); + CG_ImpactMark( cgs.media.compressionMarkShader, origin, normal, random()*360, 1,1,1,1.0, qfalse, + random() * 16 + 48, qfalse ); + + CG_ExplosionEffects( origin, 3.0f, 256 ); + + // One big bright quick flash + FX_AddSprite( origin, NULL, qfalse, 200, -200, 1.0, 1.0, 0, 0, 400, cgs.media.sunnyFlareShader); + + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->altHitSound ); +} + +qboolean FX_QuantumSparkle( localEntity_t *le) +{ + int t, i; + vec3_t org, v; + + for ( i = 0; i < 4; i ++ ) + { + VectorCopy( le->refEntity.origin, org ); + + for ( t = 0; t < 3; t++ ) + { + org[t] = le->refEntity.origin[t] + crandom() * 12; + v[t] = crandom() * 18.0f; + } + + FX_AddSprite( org, v, qfalse, random() * 1 + 1, -4, 0.5f, 1.0f, 0.0f, 0.0f, 125 + random() * 100, cgs.media.yellowParticleShader); + } + return qtrue; +} + +void FX_QuantumFizzles( vec3_t origin ) +{ + float v; + vec3_t dir, vel, org; + int i; + + for ( i = 0; i < 32; i++ ) + { + v = random() * 6.0f + 6.0f; + + VectorSet( dir, crandom(), crandom(), crandom() ); + VectorNormalize( dir ); + VectorScale( dir, v, vel ); + + org[0] = origin[0] + dir[0] * 48; + org[1] = origin[1] + dir[1] * 48; + org[2] = origin[2] + dir[2] * 64; + + FX_AddSpawner( org, dir, vel, NULL, qfalse, 125, 10 + random() * 30, 200 + random() * 400, FX_QuantumSparkle, 1024 ); + } +} + +void FX_QuantumColumns( vec3_t origin ) +{ + vec3_t dir, bottom, top; + vec3_t sizeMin = {-4, -4, -1}; + vec3_t sizeMax = {-4, -4, 1}; + trace_t trace; + localEntity_t *le; + + //Orient the explosions to face the camera + VectorSubtract( cg.refdef.vieworg, origin, dir ); + VectorNormalize( dir ); + + //=== Sound === + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.quantumBoom ); + + //=== columns === + VectorCopy( origin, bottom ); + bottom[2] -= 256; + + trap_CM_BoxTrace( &trace, origin, bottom, sizeMin, sizeMax, 0, MASK_OPAQUE ); + VectorCopy( trace.endpos, bottom ); + + VectorCopy( origin, top ); + top[2] += 256; + + trap_CM_BoxTrace( &trace, origin, top, sizeMin, sizeMax, 0, MASK_OPAQUE ); + VectorCopy( trace.endpos, top ); + + //found floor and ceiling, now do columns and ring explosions: + //ceiling + VectorSet( dir, 0, 0, -1 ); + + le = FX_AddCylinder( top, dir, top[2] - origin[2], (origin[2] - top[2]), 40, 100, 20, 50, 1.0, 0.0, 1000, cgs.media.quantumRingShader, 1.5 ); + + le->refEntity.data.cylinder.wrap = qtrue; + le->refEntity.data.cylinder.stscale = 6; + + //floor + VectorSet( dir, 0, 0, 1 ); + + le = FX_AddCylinder( bottom, dir, origin[2] - bottom[2], (bottom[2] - origin[2]), 40, 100, 20, 50, 1.0, 0.0, 1000, cgs.media.quantumRingShader, 1.5 ); + le->refEntity.data.cylinder.wrap = qtrue; + le->refEntity.data.cylinder.stscale = 6; + + FX_QuantumFizzles( origin ); + + // Main explosion, tag with light + + le = CG_MakeExplosion2( origin, dir, (qhandle_t)0, 1, cgs.media.quantumExplosionShader, 600, qtrue, 3 + crandom(), 0 ); + le->light = 150; + le->refEntity.renderfx |= RF_NOSHADOW; + VectorSet( le->lightColor, 1.0f, 1.0f, 0.6f ); + + +} diff --git a/cgame/fx_scavenger.c b/cgame/fx_scavenger.c new file mode 100644 index 0000000..0b141f1 --- /dev/null +++ b/cgame/fx_scavenger.c @@ -0,0 +1,458 @@ +#include "cg_local.h" +#include "fx_local.h" + + +/* +------------------------- +FX_HypoSpray +Redtechie: RPG-X Added +FIXME! FIXME! FIXME! FIXME! Im not spraying in the direction some one shoots me! +TiM: Fixed! An improperly formatted directional vector was being sent. it's all good now :) +------------------------- +*/ + +#define NUM_HYPO_PUFFS 20 + +void FX_HypoSpray( vec3_t origin, vec3_t dir, qboolean red ) // When not red, it'll be blue +{ + vec3_t color, vel, accel, angles, work; + float scale, dscale; + int i; + localEntity_t *le; + + vectoangles( dir, angles ); + + for ( i = 0; i < NUM_HYPO_PUFFS; i++ ) + { + if ( red ) + { + VectorSet( color, 1.0f, random() * 0.4f, random() * 0.4f ); // mostly red + } + else + { + VectorSet( color, random() * 0.5f, random() * 0.5f + 0.5f, 1.0f ); // mostly blue + } + + VectorCopy( angles, work ); + + work[0] += crandom() * 12.0f; + work[1] += crandom() * 12.0f; + + AngleVectors( work, vel, NULL, NULL ); + + scale = random() * 256.0f + 128.0f; + + VectorScale( vel, scale, vel ); + VectorScale( vel, random() * -0.3f, accel ); + + scale = random() * 4.0f + 2.0f; + dscale = random() * 64.0f + 24.0f; + + //localEntity_t *FX_AddSprite(vec3_t origin, vec3_t velocity, qboolean gravity, float scale, float dscale, + // float startalpha, float endalpha, float roll, float elasticity, + // float killTime, qhandle_t shader); + + le = FX_AddSprite( origin, vel, qfalse, scale, dscale, 0.8f + random() * 0.2f, 0.0f, crandom() * 50, /*crandom() * 5*/0, 1000, cgs.media.steamShader ); + VectorSet(le->data.sprite.startRGB, random() * 0.5f, random() * 0.5f + 0.5f, 1.0f );// mostly blue + } +} +/* +void FX_HypoSpray( vec3_t origin, vec3_t dir, qboolean red ) // When not red, it'll be blue +{ + localEntity_t *le; + vec3_t color, vel, accel, angles, work, forward, right; + float scale, dscale; + int i; + //vectoangles( dir, angles ); + VectorCopy( dir, angles ); + + //RPG-X: RedTechie - Debug print + //Com_Printf("dir: %f, %f, %f\nangles: %f, %f, %f\n\n",dir[0],dir[1],dir[2],angles[0],angles[1],angles[2]); + + for ( i = 0; i < NUM_HYPO_PUFFS; i++ ) + { + if ( red ) + { + VectorSet( color, 1.0f, random() * 0.4f, random() * 0.4f ); // mostly red + } + else + { + VectorSet( color, random() * 0.5f, random() * 0.5f + 0.5f, 1.0f ); // mostly blue + } + + VectorCopy( angles, work ); + + work[0] += crandom() * 12.0f; + work[1] += crandom() * 12.0f; + + + scale = random() * 256.0f + 128.0f; + VectorScale( vel, scale, vel ); + VectorScale( vel, random() * -0.3f, accel ); + + //scale = 30.0f + random() * 100.0f + 2.0f; + //dscale = 30.0f + random() * 400.0f + 24.0f; + scale = random() * 4.0f + 2.0f; + dscale = random() * 64.0f + 24.0f; + + le = FX_AddSprite( origin, //vec3_t origin, + vel,//vel, //vec3_t velocity, + qfalse, // qboolean gravity, + scale,//scale, //float scale, + dscale, //float dscale, + 0.8f + random() * 0.2f, //float startalpha + 0.0f, //float endalpha + crandom() * 120,//crandom() * 120, ///float roll + 0.0f, //float elasticity + 4000, //float killTime -9999999 -1000 + cgs.media.steamShader );//qhandle_t shader + //le->endTime = 99999999; + //le->color = color; + VectorSet(le->data.sprite.startRGB, random() * 0.5f, random() * 0.5f + 0.5f, 1.0f );// mostly blue + //le->data.sprite.startRGB = color; + //le->data.sprite.dRGB = color; + //FX_AddSprite( origin, vel, qfalse, scale, dscale, 0.8f + random() * 0.2f, 0.0f, color, color, crandom() * 120, 0.0f, 1000, cgs.media.steamShader ); + } +} + +*/ +//RPG-X: J2J - Fixed Version (incomplete right now) +/*void FX_HypoSpray( vec3_t origin, vec3_t dir, qboolean red ) // When not red, it'll be blue +{ + localEntity_t *le; + vec3_t muzzle, /*mins, maxs, end, color, forward, right; + float scale, dscale; + int i; + + // Move out to the end of the nozzle + //VectorMA( muzzle, 20, forward, muzzle ); + //VectorMA( muzzle, 4, vright, muzzle ); + + VectorCopy(dir, muzzle); + + AngleVectors( dir, forward, right, NULL ); + + for ( i = 0; i < NUM_HYPO_PUFFS; i++ ) + { + + if ( red ) + { + VectorSet( color, 1.0f, random() * 0.4f, random() * 0.4f ); // mostly red + } + else + { + VectorSet( color, random() * 0.5f, random() * 0.5f + 0.5f, 1.0f ); // mostly blue + } + + + VectorMA( muzzle, 24, forward, end ); + VectorSet( maxs, 6, 6, 6 ); + VectorScale( maxs, -1, mins ); + // Create the effect -- thought something was needed here, but apparently not. + VectorMA( muzzle, 20, forward, muzzle );w + VectorMA( muzzle, 4, right, muzzle ); + + + scale = random() + 2.0f; + dscale = random() * 64.0f + 24.0f; + + muzzle[0] += (scale * cos(abs(dir[1]) * 0.017453292222222222222222222222222); + muzzle[1] += (scale * sin(abs(dir[1]) * 0.017453292222222222222222222222222); + muzzle[2] += (scale * -tan(dir[0] * 0.017453292222222222222222222222222); + + VectorScale( muzzle, scale, muzzle ); + + le = FX_AddSprite( origin, //vec3_t origin, + muzzle,//vel, //vec3_t velocity, + qfalse, // qboolean gravity, + scale,//scale, //float scale, + dscale, //float dscale, + 0.8f + random() * 0.2f, //float startalpha + 0.0f, //float endalpha + crandom() * 120,//crandom() * 120, ///float roll + 0.0f, //float elasticity + 4000, //float killTime -9999999 -1000 + cgs.media.steamShader );//qhandle_t shader + + VectorSet(le->data.sprite.startRGB, random() * 0.5f, random() * 0.5f + 0.5f, 1.0f );// mostly blue + + + } + + return; + +}*/ + +//#define SCAV_SPIN 0.3 + +/*void FX_ScavengerProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + qboolean fired_from_NPC = qfalse; // Always + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0 ) + forward[2] = 1; + + // The effect you'd see from first person looks horrible from third person..or when shot by an NPC, + // so we'll just tone down the effect so it's not so horrible. :) + if ( fired_from_NPC ) + { + // Energy glow + /*FX_AddSprite( cent->lerpOrigin, + NULL, qfalse, + 8.0f + random() * 8.0f, 0.0f, + 0.7f, 0.0f, + random()*360, 0.0f, + 1.0f, + cgs.media.tetrionFlareShader ); + + // Spinning projectile core + FX_AddSprite( cent->lerpOrigin, + NULL, qfalse, + 8.0f + random() * 10.0f, 0.0f, + 1.0f, 0.0f, + cg.time * SCAV_SPIN, 0.0f, + 1.0f, + cgs.media.redFlareShader ); + + // leave a cool tail + /*FX_AddTrail( cent->lerpOrigin, + forward, qfalse, + 16, 0, + 1.0f, 0.0f, + 0.8f, 0.0f, + 0, + 1, + cgs.media.tetrionTrail2Shader ); + } + else + { + // Energy glow + /*FX_AddSprite( cent->lerpOrigin, + NULL, qfalse, + 16.0f + random() * 16.0f, 0.0f, + 0.5f, 0.0f, + random()*360, 0.0f, + 1.0f, + cgs.media.tetrionFlareShader ); + + // Spinning projectile + FX_AddSprite( cent->lerpOrigin, + NULL, qfalse, + 4.0f + random() * 10.0f, 0.0f, + 0.6f, 0.0f, + cg.time * SCAV_SPIN, 0.0f, + 1.0f, + cgs.media.redFlareShader ); + + // leave a cool tail + /*FX_AddTrail( cent->lerpOrigin, + forward, qfalse, + 64, 0, + 1.4f, 0.0f, + 0.6f, 0.0f, + 0, + 1, + cgs.media.tetrionTrail2Shader ); + } +}*/ + + +/* +------------------------- +FX_ScavengerAltFireThink +------------------------- +*/ +//#define SCAV_TRAIL_SPACING 12 + +/*void FX_ScavengerAltFireThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t diff; + float len; + + // Make a trail that's reasonably consistent and not so much based on frame rate. + if (cent->thinkFlag) + { + VectorSubtract( cent->lerpOrigin, cent->beamEnd, diff ); + } + else + { + VectorSubtract( cent->lerpOrigin, cent->currentState.origin2, diff ); + } + + len = VectorNormalize( diff ); + + if ( len > SCAV_TRAIL_SPACING ) + { + vec3_t origin; + int i; + float scale; + + for ( i = 0 ; i < len; i += SCAV_TRAIL_SPACING ) + { + // Calc the right spot along the trail + VectorMA( cent->lerpOrigin, -i, diff, origin ); + scale = 18.0f + (random()*5.0f); + /*FX_AddSprite( origin, + NULL, qfalse, + scale, -8.75, + 0.4f, 0.0f, + random() * 360, 0.0f, + 250.0f, + cgs.media.scavengerAltShader ); + } + + // Stash the current position + VectorCopy( cent->lerpOrigin, cent->beamEnd); + cent->thinkFlag = 1; + } + + // Glowing bit + /*FX_AddSprite( cent->lerpOrigin, + NULL, qfalse, + 24.0f + ( random() * 16.0f ), 0.0f, + 1.0f, 0.0f, + 0, 0.0f, + 1.0f, + cgs.media.tetrionFlareShader ); +}*/ + + +/* +------------------------- +FX_ScavengerWeaponHitWall +------------------------- +*/ +/*void FX_ScavengerWeaponHitWall( vec3_t origin, vec3_t normal, qboolean fired_by_NPC ) +{ + weaponInfo_t *weaponInfo = &cg_weapons[WP_COFFEE]; + + // Tone down when shot by an NPC + // FIXME: is this really a good idea? + if ( fired_by_NPC ) + { + // Expanding shock ring + FX_AddQuad( origin, normal, + 0.5f, 6.4f, + 0.8, 0.0, + random() * 360.0f, + 200, + cgs.media.redRingShader ); + + // Impact core + FX_AddQuad( origin, normal, + 12.0f + ( random() * 8.0f ), 3.2f, + 0.6f, 0.0f, + cg.time * SCAV_SPIN, + 100, + cgs.media.redFlareShader ); + } + else + { + // Expanding shock ring + FX_AddQuad( origin, normal, + 8.0f, 12.8f, + 1.0, 0.0, + random() * 360.0f, + 200, + cgs.media.redRingShader ); + + // Impact core + FX_AddQuad( origin, normal, + 24.0f + ( random() * 16.0f ), 6.4f, + 0.8f, 0.0f, + cg.time * SCAV_SPIN, + 100, + cgs.media.redFlareShader ); + } + + //Sound + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->mainHitSound); + + CG_ImpactMark( cgs.media.scavMarkShader, origin, normal, random()*360, 1,1,1,0.2, qfalse, random() + 5.5f, qfalse ); +}*/ + + +/* +------------------------- +FX_ScavengerWeaponHitPlayer +------------------------- +*/ +/*void FX_ScavengerWeaponHitPlayer( vec3_t origin, vec3_t normal, qboolean fired_by_NPC ) +{ + if ( fired_by_NPC ) + { + // Smaller expanding shock ring + FX_AddQuad( origin, normal, + 0.5f, 32.0f, + 0.8, 0.0, + random() * 360.0f, + 125, + cgs.media.redRingShader ); + } + else + { + // Expanding shock ring + FX_AddQuad( origin, normal, + 1.0f, 64.0f, + 0.8, 0.0, + random() * 360.0f, + 125, + cgs.media.redRingShader ); + } + + //Sound +// trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cg_weapons[WP_COFFEE].missileHitSound ); +}*/ + + + +/* +------------------------- +FX_Scavenger_Alt_Explode +------------------------- +*/ +/*void FX_ScavengerAltExplode( vec3_t origin, vec3_t dir ) +{ +// FXCylinder *fx2; + localEntity_t *le; + vec3_t direction, org; + int i; + weaponInfo_t *weaponInfo = &cg_weapons[WP_COFFEE]; + + //Orient the explosions to face the camera + VectorSubtract( cg.refdef.vieworg, origin, direction ); + VectorNormalize( direction ); + + VectorMA( origin, 12, direction, org ); + // Add an explosion and tag a light to it + //le = CG_MakeExplosion2( org, direction, cgs.media.explosionModel, 5, cgs.media.scavExplosionSlowShader, 675, qfalse, 1.0f + (random()*0.5f), LEF_NONE); + le->light = 150; + le->refEntity.renderfx |= RF_NOSHADOW; + VectorSet( le->lightColor, 1.0f, 0.6f, 0.6f ); + + VectorSet( org, (org[0] + crandom() * 8), (org[1] + crandom() * 8), (org[2] + crandom() * 8) ); + //CG_MakeExplosion2( org, direction, cgs.media.explosionModel, 5, cgs.media.scavExplosionFastShader, 375, qfalse, 0.7f + (random()*0.5f), LEF_NONE); + + //Sound + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->altHitSound ); + + CG_ImpactMark( cgs.media.compressionMarkShader, origin, dir, random()*360, 1,1,1,0.2, qfalse, + random() * 6 + 20, qfalse ); + + // Always orient horizontally + VectorSet ( direction, 0,0,1 ); + + le = FX_AddCylinder( origin, direction, 4, 0, 20, 210, 14, 140, 1.0, 0.0, 600, cgs.media.redRing2Shader, 1.5 ); + le->refEntity.data.cylinder.wrap = qtrue; + le->refEntity.data.cylinder.stscale = 6; + + for (i = 0; i < 6; i++) + { + vec3_t velocity; + + FXE_Spray( dir, 300, 175, 0.8f, velocity); + /*FX_AddTrail( origin, velocity, qtrue, 12.0f, -12.0f, + 2, -2, 1.0f, 1.0f, 0.2f, 1000.0f, cgs.media.tetrionTrail2Shader);*/ + /*} +}*/ diff --git a/cgame/fx_stasis.c b/cgame/fx_stasis.c new file mode 100644 index 0000000..835e93c --- /dev/null +++ b/cgame/fx_stasis.c @@ -0,0 +1,617 @@ +#include "cg_local.h" +#include "fx_local.h" + +void FX_StasisDischarge( vec3_t origin, vec3_t normal, int count, float dist_out, float dist_side ); + +#define FX_STASIS_ALT_RIGHT_OFS 0.10 +#define FX_STASIS_ALT_UP_OFS 0.02 +#define FX_STASIS_ALT_MUZZLE_OFS 1 +#define FX_MAXRANGE_STASIS 4096 + +/* +------------------------- +FX_StasisShot + +Alt-fire, beam that shrinks to its impact point +------------------------- +*/ + +/*void FX_SmallStasisBeam(centity_t *cent, vec3_t start, vec3_t dir) +{ + vec3_t end, org, vel = { 0,0,-4}; + trace_t tr; + float r; + int i, ct, t; + + VectorMA(start, FX_MAXRANGE_STASIS, dir, end); + CG_Trace(&tr, start, NULL, NULL, end, cent->currentState.number, MASK_SHOT); + + // Beam +// FX_AddLine( start, tr.endpos, 1.0f, 3.0f, 4.0f, 0.8f, 0.0f, 400.0f, cgs.media.stasisAltShader); + + // Do a quick LOD for number of decay particles + ct = tr.fraction * (FX_MAXRANGE_STASIS * 0.02); + if ( ct < 12 ) + ct = 12; + else if ( ct > 24 ) + ct = 24; + + for ( i = 0; i < ct; i++ ) + { + r = random() * tr.fraction * (FX_MAXRANGE_STASIS * 0.5); + VectorMA( start, r, dir, org ); + + for ( t = 0; t < 3; t++ ) + org[t] += crandom(); + + if ( rand() & 1 ) + FX_AddSprite( org, vel, qfalse, random() + 1.5, -3, 1.0, 1.0, 0.0, 0.0, 500, cgs.media.blueParticleShader); + else + FX_AddSprite( org, vel, qfalse, random() + 1.5, -3, 1.0, 1.0, 0.0, 0.0, 500, cgs.media.purpleParticleShader); + } + + // Impact graphic if needed. + if (cg_entities[tr.entityNum].currentState.eType == ET_PLAYER) + { // Hit an entity. + // Expanding rings +// FX_AddSprite( tr.endpos, NULL, qfalse, 1, 60, 0.8, 0.2, random() * 360, 0, 400, cgs.media.stasisRingShader ); + // Impact effect +// FX_AddSprite( tr.endpos, NULL, qfalse, 7, 25, 1.0, 0.0, random() * 360, 0, 500, cgs.media.blueParticleShader ); + FX_AddSprite( tr.endpos, NULL, qfalse, 5, 18, 1.0, 0.0, random() * 360, 0, 420, cgs.media.ltblueParticleShader ); + } + else if (!(tr.surfaceFlags & SURF_NOIMPACT) ) + { + // Move me away from the wall a bit so that I don't z-buffer into it + VectorMA( tr.endpos, 1.5, tr.plane.normal, end); + + // Expanding rings +// FX_AddQuad( end, tr.plane.normal, 1, 12, 0.8, 0.2, random() * 360, 400, cgs.media.stasisRingShader ); +// FX_AddQuad( end, tr.plane.normal, 1, 30, 0.8, 0.2, random() * 360, 300, cgs.media.stasisRingShader ); + // Impact effect + FX_AddQuad( end, tr.plane.normal, 4, 16, 1.0, 0.0, random() * 360, 500, cgs.media.blueParticleShader ); + FX_AddQuad( end, tr.plane.normal, 3, 12, 1.0, 0.0, random() * 360, 420, cgs.media.ltblueParticleShader ); + + CG_ImpactMark( cgs.media.scavMarkShader, end, tr.plane.normal, random()*360, 1,1,1,0.6, qfalse, + 5 + random() * 2, qfalse ); + } + + FX_AddSprite( tr.endpos, NULL, qfalse, flrandom(40,60), -50, 1.0, 0.0, random() * 360, 0, 500, cgs.media.blueParticleShader ); + + // Pass the end position back to the calling function (yes, I know). + VectorCopy(tr.endpos, dir); +}*/ + + +// kef -- fixme. the altfire stuff really wants to use some endcap stuff and some other flags +/*void FX_StasisShot( centity_t *cent, vec3_t end, vec3_t start ) +{ + trace_t tr; + vec3_t fwd, newdir, org, vel = { 0,0,-4}, newstart, end2; + int i, t, ct; + float len, r; + vec3_t fwd2, right, up; + int bolt1, bolt2; + vec3_t bolt1vec, bolt2vec; + centity_t *traceEnt = NULL; + int clientNum = -1; + + // Choose which bolt will have the electricity accent. + bolt1 = irandom(0,2); + bolt2 = irandom(0,4); + + VectorSubtract( end, start, fwd ); + len = VectorNormalize( fwd ); + + // Beam +// FX_AddLine( end, start, 1.0f, 4.0f, 6.0f, 0.8f, 0.0f, 500.0f, cgs.media.stasisAltShader); + + // Do a quick LOD for number of decay particles + ct = len * 0.03; + if ( ct < 16 ) + ct = 16; + else if ( ct > 32 ) + ct = 32; + + for ( i = 0; i < ct; i++ ) + { + r = random() * len * 0.5; + VectorMA( start, r, fwd, org ); + + for ( t = 0; t < 3; t++ ) + org[t] += crandom(); + + if ( rand() & 1 ) + FX_AddSprite( org, vel, qfalse, random() + 2, -4, 1.0, 1.0, 0.0, 0.0, 600, cgs.media.blueParticleShader); + else + FX_AddSprite( org, vel, qfalse, random() + 2, -4, 1.0, 1.0, 0.0, 0.0, 600, cgs.media.purpleParticleShader); + } + VectorMA(start, FX_MAXRANGE_STASIS, fwd, end2); + CG_Trace(&tr, start, NULL, NULL, end2, cent->currentState.number, MASK_SHOT); + if (!( tr.surfaceFlags & SURF_NOIMPACT )) + { + traceEnt = &cg_entities[tr.entityNum]; + clientNum = traceEnt->currentState.clientNum; + if ( (tr.entityNum != ENTITYNUM_WORLD) && (clientNum >= 0 || clientNum < MAX_CLIENTS) ) + { + // hit a player + FX_StasisShotImpact(tr.endpos, tr.plane.normal); + } + else + { + // hit the world + FX_StasisShotMiss(tr.endpos, tr.plane.normal); + } + } + // cap the impact end of the main beam to hide the nasty end of the line + FX_AddSprite( tr.endpos, NULL, qfalse, flrandom(40,60), -50, 1.0, 0.0, random() * 360, 0, 500, cgs.media.blueParticleShader ); + + if (bolt1==0) + { + VectorCopy(end, bolt1vec); + } + else if (bolt2==0) + { + VectorCopy(end, bolt2vec); + } + + AngleVectors(cent->currentState.angles, fwd2, right, up); + +// CrossProduct(fwd, up, right); +// VectorNormalize(right); // "right" is scaled by the sin of the angle between fwd & up... Ditch that. +// CrossProduct(right, fwd, up); // Change the "fake up" (0,0,1) to a "real up" (perpendicular to the forward vector). + // VectorNormalize(up); // If I cared about how the vertical variance looked when pointing up or down, I'd normalize this. + + // Fire a shot up and to the right. + VectorMA(fwd, FX_STASIS_ALT_RIGHT_OFS, right, newdir); + VectorMA(newdir, FX_STASIS_ALT_UP_OFS, up, newdir); + VectorMA(start, FX_STASIS_ALT_MUZZLE_OFS, right, newstart); + FX_SmallStasisBeam(cent, newstart, newdir); + + if (bolt1==1) + { + VectorCopy(newdir, bolt1vec); + } + else if (bolt2==1) + { + VectorCopy(newdir, bolt2vec); + } + + // Fire a shot up and to the left. + VectorMA(fwd, -FX_STASIS_ALT_RIGHT_OFS, right, newdir); + VectorMA(newdir, FX_STASIS_ALT_UP_OFS, up, newdir); + VectorMA(start, -FX_STASIS_ALT_MUZZLE_OFS, right, newstart); + FX_SmallStasisBeam(cent, newstart, newdir); + + if (bolt1==2) + { + VectorCopy(newdir, bolt1vec); + } + else if (bolt2==2) + { + VectorCopy(newdir, bolt2vec); + } + + // Fire a shot a bit down and to the right. + VectorMA(fwd, 2.0*FX_STASIS_ALT_RIGHT_OFS, right, newdir); + VectorMA(newdir, -0.5*FX_STASIS_ALT_UP_OFS, up, newdir); + VectorMA(start, 2.0*FX_STASIS_ALT_MUZZLE_OFS, right, newstart); + FX_SmallStasisBeam(cent, newstart, newdir); + + if (bolt1==3) + { + VectorCopy(newdir, bolt1vec); + } + else if (bolt2==3) + { + VectorCopy(newdir, bolt2vec); + } + + // Fire a shot up and to the left. + VectorMA(fwd, -2.0*FX_STASIS_ALT_RIGHT_OFS, right, newdir); + VectorMA(newdir, -0.5*FX_STASIS_ALT_UP_OFS, up, newdir); + VectorMA(start, -2.0*FX_STASIS_ALT_MUZZLE_OFS, right, newstart); + FX_SmallStasisBeam(cent, newstart, newdir); + + if (bolt1==4) + { + VectorCopy(newdir, bolt1vec); + } + else if (bolt2==4) + { + VectorCopy(newdir, bolt2vec); + } + + // Put a big gigant-mo sprite at the muzzle end so people can't see the crappy edges of the line + FX_AddSprite( start, NULL, qfalse, random()*3 + 15, -20, 1.0, 0.5, 0.0, 0.0, 600, cgs.media.blueParticleShader); + + // Do an electrical arc to one of the impact points. + FX_AddElectricity( start, bolt1vec, 0.2f, 15.0, -15.0, 1.0, 0.5, 100, cgs.media.dnBoltShader, 0.1 ); + + if (bolt1!=bolt2) + { + // ALSO do an electrical arc to another point. + FX_AddElectricity( bolt1vec, bolt2vec, 0.2f, 15.0, -15.0, 1.0, 0.5, flrandom(100,200), cgs.media.dnBoltShader, 0.5 ); + } +}*/ + +/* +------------------------- +FX_StasisShotImpact + +Alt-fire, impact effect +------------------------- +*/ +/*void FX_StasisShotImpact( vec3_t end, vec3_t dir ) +{ + vec3_t org; + + // Move me away from the wall a bit so that I don't z-buffer into it + VectorMA( end, 1.5, dir, org ); + + // Expanding rings +// FX_AddQuad( org, dir, 1, 80, 0.8, 0.2, random() * 360, 400, cgs.media.stasisRingShader ); + // Impact effect + FX_AddQuad( org, dir, 7, 35, 1.0, 0.0, random() * 360, 500, cgs.media.blueParticleShader ); + FX_AddQuad( org, dir, 5, 25, 1.0, 0.0, random() * 360, 420, cgs.media.ltblueParticleShader ); + +// CG_ImpactMark( cgs.media.scavMarkShader, org, dir, random()*360, 1,1,1,0.6, qfalse, +// 8 + random() * 2, qfalse ); + +// FX_StasisDischarge( org, dir, irandom( 2,4 ), 24 + random() * 12, 64 + random() * 48 ); +}*/ + +/* +------------------------- +FX_StasisShotMiss + +Alt-fire, miss effect +------------------------- +*/ +/*void FX_StasisShotMiss( vec3_t end, vec3_t dir ) +{ + vec3_t org; + + // Move me away from the wall a bit so that I don't z-buffer into it + VectorMA( end, 0.5, dir, org ); + + // Expanding rings +// FX_AddQuad( org, dir, 1, 16, 0.8, 0.2, random() * 360, 400, cgs.media.stasisRingShader ); +// FX_AddQuad( org, dir, 1, 40, 0.8, 0.2, random() * 360, 300, cgs.media.stasisRingShader ); + // Impact effect + FX_AddQuad( org, dir, 5, 25, 1.0, 0.0, random() * 360, 500, cgs.media.blueParticleShader ); + FX_AddQuad( org, dir, 4, 17, 1.0, 0.0, random() * 360, 420, cgs.media.ltblueParticleShader ); + + CG_ImpactMark( cgs.media.scavMarkShader, org, dir, random()*360, 1,1,1,0.6, qfalse, + 6 + random() * 2, qfalse ); + + FX_AddSprite( end, NULL, qfalse, flrandom(40,60), -50, 1.0, 0.0, random() * 360, 0, 500, cgs.media.blueParticleShader ); + +// FX_StasisDischarge( org, dir, irandom( 2,4 ), 24 + random() * 12, 64 + random() * 48 ); +}*/ + +/* +------------------------- +FX_StasisProjectileThink + +Main fire, with crazy bits swirling around main projectile +Hehe used to :D -TiM +------------------------- +*/ +//unused +/*void FX_StasisProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + int size = 0; + vec3_t forward; + //vec3_t right, up; + //float radius, temp; + vec3_t org; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0 ) + forward[2] = 1; + + VectorCopy( cent->lerpOrigin, org ); + +// org[0] -=32; + +//FX_AddTrail( + + FX_AddTrail( org, forward, qfalse, 64, 0, 30.4f, 0.0f, 0.6f, 0.0f, 0, 1, cgs.media.disruptorBolt ); + + FX_AddSprite( cent->lerpOrigin, NULL, qfalse, 25.0f, 0.0f, 0.7f, 0.0f, 1.0f, 0.0f, 1.0f, cgs.media.disruptorStreak ); + + +}*/ + +/* +------------------------- +FX_OrientedBolt + +Creates new bolts for a while +------------------------- +*/ + +void FX_OrientedBolt( vec3_t start, vec3_t end, vec3_t dir ) +{ + vec3_t mid; + + VectorSubtract( end, start, mid ); + VectorScale( mid, 0.1f + (random() * 0.8), mid ); + VectorAdd( start, mid, mid ); + VectorMA(mid, 3.0f + (random() * 10.0f), dir, mid ); + + //FX_AddElectricity( mid, start, 0.5, 0.75 + random() * 0.75, 0.0, 1.0, 0.5, 300.0f + random() * 300, cgs.media.bolt2Shader, DEFAULT_DEVIATION); + //FX_AddElectricity( mid, end, 0.5, 0.75 + random() * 0.75, 1.0, 1.0, 0.5, 300.0f + random() * 300, cgs.media.bolt2Shader, DEFAULT_DEVIATION); + + FX_AddElectricity( mid, start, 0.5, 0.75 + random() * 0.75, 0.0, 1.0, 0.5, 300.0f + random() * 300, cgs.media.borgLightningShaders[2], DEFAULT_DEVIATION); + FX_AddElectricity( mid, end, 0.5, 0.75 + random() * 0.75, 1.0, 1.0, 0.5, 300.0f + random() * 300, cgs.media.borgLightningShaders[3], DEFAULT_DEVIATION); +} + +/* +------------------------- +FX_StasisDischarge + +Fun "crawling" electricity ( credit goes to Josh for this one ) +------------------------- +*/ + +void FX_StasisDischarge( vec3_t origin, vec3_t normal, int count, float dist_out, float dist_side ) +{ + trace_t trace; + vec3_t org, dir, dest; + vec3_t vr; + int i; + int discharge = dist_side; + + vectoangles( normal, dir ); + dir[ROLL] += random() * 360; + + for (i = 0; i < count; i++) + { + //Move out a set distance + VectorMA( origin, dist_out, normal, org ); + + //Even out the hits + dir[ROLL] += (360 / count) + (rand() & 31); + AngleVectors( dir, NULL, vr, NULL ); + + //Move to the side in a random direction + discharge += (int)( crandom() * 8.0f ); + VectorMA( org, discharge, vr, org ); + + //Trace back to find a surface + VectorMA( org, -dist_out * 3, normal, dest ); + + CG_Trace( &trace, org, NULL, NULL, dest, 0, MASK_SHOT ); + + //No surface found, start over + if (trace.fraction == 1) + continue; + + //Connect the two points with bolts + FX_OrientedBolt( origin, trace.endpos, normal ); + + //TiM : Aww screw it. Add a lens flare. ^_^ + CG_InitLensFlare( trace.endpos, + 10, 10, + colorTable[CT_GREEN], 1.2, 2.0, 1600, 500, + colorTable[CT_GREEN], 1600, 500, 100, 5, qtrue, + 0, 0, qfalse, qtrue, + qfalse, 1.0, cg.time, 0, 0, 300.0f + random() * 300); + } +} + +/* +------------------------- +FX_StasisWeaponHitWall + +Main fire impact +------------------------- +*/ + +#define NUM_DISCHARGES 6 +#define DISCHARGE_DIST 8 +#define DISCHARGE_SIDE_DIST 24 + +void FX_StasisWeaponHitWall( vec3_t origin, vec3_t dir, int size ) +{ + vec3_t vel, /*accel,*/ hitpos, direction, org; + //int i, t; + weaponInfo_t *weaponInfo = &cg_weapons[WP_DISRUPTOR]; + + CG_InitLensFlare( origin, + 375, 375, + colorTable[CT_GREEN], 1.2, 2.0, 1600, 200, + colorTable[CT_GREEN], 1600, 200, 800, 20, qtrue, + 0, 0, qfalse, qtrue, + qfalse, 1.0, cg.time, 0, 0, 200); + + // Generate "crawling" electricity // eh, don't it doesn't look that great. + FX_StasisDischarge( origin, dir, NUM_DISCHARGES, DISCHARGE_DIST, DISCHARGE_SIDE_DIST ); + + VectorMA(origin, size, dir, hitpos); + + // Set an oriented residual glow effect + FX_AddQuad( hitpos, dir, size * size * 15.0f, -150.0f, + 1.0f, 0.0f, 0, 300, cgs.media.greenParticleShader ); + + CG_ImpactMark( cgs.media.scavMarkShader, origin, dir, random()*360, 1,1,1,0.6, qfalse, + size * 12 + 1, qfalse ); + + FX_AddSprite( hitpos, NULL, qfalse, size * size * 15.0f, -150.0f, + 1.0f, 0.0f, 360*random(), 0, 400, cgs.media.greenParticleShader ); + +/* FX_AddSprite( hitpos, NULL, qfalse, size * size * 15.0f, -150.0f, + 1.0f, 0.0f, 360*random(), 0, 400, cgs.media.greenParticleStreakShader ); */ + + FX_AddSprite( hitpos, NULL, qfalse, size * size * 25.0f, -150.0f, + 1.0f, 0.0f, 0.0f, 0, 400, cgs.media.greenParticleStreakShader ); + + VectorSubtract( cg.refdef.vieworg, origin, direction ); + VectorNormalize( direction ); + + VectorMA( origin, 12, direction, org ); + VectorMA( org, 8, dir, direction ); + VectorSet(vel, 0, 0, 32 ); //8 + + FX_AddSprite( origin, + vel, qfalse, + random() * 4 + 2, 12, + 0.6 + random() * 0.4, 0.0, + random() * 180, + 0.0, + random() * 200 + 1200, //300 + cgs.media.steamShader ); + + //FX_AddSprite( + + // Only play the impact sound and throw off the purple particles when it's the main projectile +/* if ( size < 3 ) + return; + + for ( i = 0; i < 4; i++ ) + { + for ( t = 0; t < 3; t++ ) + vel[t] = ( dir[t] + crandom() * 0.9 ) * ( random() * 100 + 250 ); + + VectorScale( vel, -2.2, accel ); + FX_AddSprite( hitpos, vel, qfalse, random() * 8 + 8, 0, 1.0, 0.0, 0.0, 0.0, 200, cgs.media.purpleParticleShader ); + + }*/ + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, weaponInfo->mainHitSound ); +} + +void FX_DisruptorBeamFire( vec3_t startpos, vec3_t endpos, vec3_t normal, qboolean spark, qboolean impact, qboolean empty ) +{ + refEntity_t beam; + sfxHandle_t sfx; + float size; + vec3_t velocity; + int sparks; + vec3_t rgb = { 1,0.9,0.6}, rgb2={1,0.3,0}; + + //vec3_t rgb3 = { 1.0, 1.0, 1.0 }; + + sfx = 0; + + // Draw beam first. + memset( &beam, 0, sizeof( beam ) ); + + VectorCopy( startpos, beam.origin); + VectorCopy( endpos, beam.oldorigin ); + beam.reType = RT_LINE; + if (empty) + { + beam.customShader = cgs.media.phaserEmptyShader; + } + else + { + beam.customShader = cgs.media.disruptorBeam; + } + AxisClear( beam.axis ); + beam.shaderRGBA[0] = 0xff; + beam.shaderRGBA[1] = 0xff; + beam.shaderRGBA[2] = 0xff; + beam.shaderRGBA[3] = 0xff; + if (empty) + { + beam.data.line.width = 1.0f + ( crandom() * 0.6f ); + } + else + { + beam.data.line.width = 1.5f + ( crandom() * 0.6f ); + } + beam.data.line.stscale = 5.0; + trap_R_AddRefEntityToScene( &beam ); + + // Now draw the hit graphic + + // no explosion at LG impact, it is added with the beam + + if ( sfx ) + { + Com_Printf("playing %s\n", "phaser sound"); + trap_S_StartSound( endpos, ENTITYNUM_WORLD, CHAN_AUTO, sfx ); + } + + // + // impact mark + // + if (impact) + { + if (!empty) + { // normal. + CG_ImpactMark( cgs.media.scavMarkShader, endpos, normal, random()*360, 1,1,1,0.2, qfalse, + random() + 1, qfalse ); + + //VectorCopy( endpos, phaserFlare.worldCoord ); + + /*CG_InitLensFlare( endpos, + 80, + 80, + rgb, + 1.2, + 1.5, + 1600, + 200, + colorTable[CT_BLACK], + 1600, + 200, + 80, + 5, + qfalse, + 5, + 40, + qfalse, + qfalse, + qfalse, + 1.0, + 1.0, + 200.0, + 200.0, + 200.0 );*/ + + //CG_InitLensFlare( endpos, + // 30, 30, + // rgb, 1.2, 2.0, 1600, 200, + // colorTable[CT_BLACK], 1600, 200, 410, 15, qfalse, + // 0, 0, qfalse, qtrue, + // qfalse, 1.0, cg.time, 0, 0, 50); + + //TiM : Add your basic cheesy 'seen-way-too-much-in-movies-these-days' anamorphic lens streak :) + //CG_DrawLensFlare( &phaserFlare ); + //FX_AddSprite( endpos, NULL, qfalse, random() * 1.25 + 5.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 50.0, cgs.media.blueParticleStreakShader ); //1.5f + + //FX_AddQuad2( endpos, normal, random() * 1.25 + 8.0f, 0.0f, 1.0f, 1.0f, rgb3, rgb3, 270, 50.0, cgs.media.blueParticleStreakShader ); + //eh... looked bad :P + + FX_AddQuad2( endpos, normal, random() * 1.25 + 1.5f, 0.0f, 1.0f, 0.0f, rgb, rgb2, rand() % 360, 500 + random() * 200, + cgs.media.sunnyFlareShader ); + } + else + { // Wuss hit when empty. + FX_AddQuad2( endpos, normal, random() * .75 + 1.0f, 0.0f, 0.5f, 0.0f, rgb, rgb2, rand() % 360, 300 + random() * 200, + cgs.media.sunnyFlareShader ); + } + } + + // "Fun" sparks... Not when empty. + if ( spark && !empty) + { + sparks = rand() & 1 + 1; + for(;sparks>0;sparks--) + { + size = 0.2f + (random() * 0.4); + FXE_Spray( normal, 200, 75, 0.8f, velocity); + if (rand() & LEF_USE_COLLISION) + { // This spark bounces. + FX_AddTrail( endpos, velocity, qtrue, 5.0f, -15.0f, + size, -size, 1.0f, 0.5f, 0.4f, 500.0f, cgs.media.sparkShader); + } + else + { + FX_AddTrail( endpos, velocity, qtrue, 5.0f, -15.0f, + size, -size, 1.0f, 0.5f, 0.0, 500.0f, cgs.media.sparkShader); + } + } + } +} diff --git a/cgame/fx_tetrion.c b/cgame/fx_tetrion.c new file mode 100644 index 0000000..177d971 --- /dev/null +++ b/cgame/fx_tetrion.c @@ -0,0 +1,240 @@ +#include "cg_local.h" +#include "fx_local.h" + + +/* +------------------------- +FX_TetrionProjectileThink +------------------------- +*/ +/*void FX_TetrionProjectileThink( centity_t *cent, const struct weaponInfo_s *wi ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0 ) + forward[2] = 1; + + /*FX_AddSprite( cent->lerpOrigin, + NULL, qfalse, + 4.0f + random() * 16.0f, 0.0f, + 0.4f, 0.0f, + random()*360, 0.0f, + 1.0f, + cgs.media.greenBurstShader ); + FX_AddSprite( cent->lerpOrigin, + NULL, qfalse, + 16.0f + random() * 16.0f, 0.0f, + 0.6f, 0.0f, + random()*360, 0.0f, + 1.0f, + cgs.media.borgFlareShader ); + /*FX_AddTrail( cent->lerpOrigin, + forward, qfalse, + 64, 0, + 2.0f, 0, + 0.5f, 0, + 0, + 1, + cgs.media.greenTrailShader ); +}*/ + + +/* +------------------------- +FX_TetrionShot +------------------------- +*/ +#define MAXRANGE_TETRION 5000000 //RPG-X: J2J OLD: 8192 +void FX_TetrionShot( vec3_t start, vec3_t forward ) +{ + trace_t trace; + vec3_t end, dir, new_start, new_end, radial, start2, spreadFwd; + float off, len, i, numBullets = 3; + float firingRadius = 6, minDeviation = 0.95, maxDeviation = 1.1; + qboolean render_impact = qtrue; + centity_t *traceEnt = NULL; + int clientNum = -1; + + for (i = 0; i < numBullets; i++) + { + render_impact = qtrue; + // determine new firing position + fxRandCircumferencePos(start, forward, firingRadius, new_start); + VectorSubtract(new_start, start, radial); + VectorMA(start, 10, forward, start2); + VectorMA(start2, flrandom(minDeviation, maxDeviation), radial, start2); + VectorSubtract(start2, new_start, spreadFwd); + VectorNormalize(spreadFwd); + // determine new end position for this bullet. give the endpoint some spread, too. + VectorMA(new_start, MAXRANGE_TETRION, spreadFwd, end); + CG_Trace( &trace, new_start, NULL, NULL, end, cg_entities[cg.predictedPlayerState.clientNum].currentState.number, MASK_SHOT ); + // Get the length of the whole shot + VectorSubtract( trace.endpos, new_start, dir ); + len = VectorNormalize( dir ); + // Don't do tracers when it gets really short + if ( len >= 64 ) + { + // Move the end_point in a bit so the tracer doesn't always trace the full line length--this isn't strictly necessary, but it does + // add a bit of variance + off = flrandom(0.7, 1.0); + VectorMA( new_start, len * off, dir, new_end ); + + // Draw the tracer + FX_AddLine( new_end, new_start, 1.0f, 1.5f + random(), 0.0f, flrandom(0.3,0.6), 0.0, + flrandom(300,500), cgs.media.borgFlareShader ); + } + // put the impact effect where this tracer hits + if (len >= 32) + { + // Rendering things like impacts when hitting a sky box would look bad, but you still want to see the tracer + if ( trace.surfaceFlags & SURF_NOIMPACT ) + { + render_impact = qfalse; + } + + if (render_impact) + { + traceEnt = &cg_entities[trace.entityNum]; + clientNum = traceEnt->currentState.clientNum; + if ( (trace.entityNum != ENTITYNUM_WORLD) && (clientNum >= 0 || clientNum < MAX_CLIENTS) ) + { + // hit a player. let the shield/pain effects be the indicator for this + } + else + { + // hit something else + FX_TetrionWeaponHitWall(trace.endpos, trace.plane.normal); + } + } + } + } +} + +/* +------------------------- +FX_TetrionWeaponHitWall +------------------------- +*/ +void FX_TetrionWeaponHitWall( vec3_t origin, vec3_t normal ) +{ +/* vec3_t vel, accel, org; + int i = 0, t = 0; + float scale = random() * 2.5 + 1.5; + + CG_ImpactMark( cgs.media.bulletmarksShader, origin, normal, random()*360, 1,1,1,0.2, qfalse, + scale, qfalse ); + + // Move out a hair to avoid z buffer nastiness + VectorMA( origin, 0.5, normal, org ); + + // Add a bit of variation every now and then + if ( rand() & 1 ) + { + FX_AddQuad( org, normal, + scale * 2, -4, + 0.5, 0.5, + 0, + 175, + cgs.media.sunnyFlareShader ); + } + + FX_AddQuad( org, normal, + scale * 4, -8, + 1.0, 1.0, + 0, + 175, + cgs.media.borgFlareShader ); + + // Add some smoke puffs + for ( i = 0; i < 2; i ++ ) + { + for ( t = 0; t < 3; t++ ) + { + vel[t] = normal[t] + crandom(); + } + + VectorScale( vel, 12 + random() * 12, vel ); + + vel[2] += 16; + + VectorScale( vel, -0.25, accel ); + FX_AddSprite( origin, + vel, qfalse, + random() * 4 + 2, 12, + 0.6 + random() * 0.4, 0.0, + random() * 180, + 0.0, + random() * 200 + 300, + cgs.media.steamShader ); + }*/ +} + +/* +------------------------- +FX_TetrionRicochet +------------------------- +*/ /* +void FX_TetrionRicochet( vec3_t origin, vec3_t normal ) +{ + vec3_t org; + + // Move away from the wall a bit to help avoid z buffer clipping. + VectorMA( origin, 0.5, normal, org ); + + /*FX_AddQuad( org, normal, + 24, -24, + 1.0, 0.0, + 0, + 300, + cgs.media.greenBurstShader ); + FX_AddQuad( org, normal, + 48, -48, + 0.5, 0.0, + 0, + 300, + cgs.media.borgFlareShader ); +}*/ + +/* +------------------------- +FX_TetrionAltHitWall +------------------------- +*/ +/*void FX_TetrionAltHitWall( vec3_t origin, vec3_t normal ) +{ + vec3_t org; + float scale; + + scale = random() * 2.0 + 1.0; + + /*CG_ImpactMark( cgs.media.bulletmarksShader, origin, normal, random()*360, 1,1,1,0.2, qfalse, + scale, qfalse ); + + // Move out a hair to avoid z buffer nastiness + VectorMA( origin, 0.5, normal, org ); + + /*FX_AddQuad( origin, normal, + 64, -96, + 1.0, 0.0, + 0, + 200, + cgs.media.greenBurstShader ); + FX_AddQuad( origin, normal, + 128, -192, + 1.0, 0.0, + 0, + 200, + cgs.media.borgFlareShader ); + + // kef -- urp.fixme. +// trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cg_weapons[WP_TR116].altmissileHitSound ); +}*/ + +/* +------------------------- +FX_TetrionAltHitPlayer +------------------------- +*/ +void FX_TetrionAltHitPlayer( vec3_t origin, vec3_t normal ) +{ +} diff --git a/cgame/fx_transporter.c b/cgame/fx_transporter.c new file mode 100644 index 0000000..3476dac --- /dev/null +++ b/cgame/fx_transporter.c @@ -0,0 +1,293 @@ + +#include "cg_local.h" +#include "fx_local.h" + +/* +------------------------- +SPTransporterLensFlares + +TiM: Judging from the title, +you just KNOW it's mine ;) + +Anyway, the point of this +function is to render 4 +sprites and then scale + move +them in a way reminisicant +of ST: Voyager's transporter FX. + +I wrote this instead of using the +already made FX spawning functions +because they don't let u track an origin. +To stop the particle +from rendering within the character, I'm going to +use the DEPTHHACK renderflag, the flag +originally used for the weapon models +in first-person mode. :) + +Planned timing: + +0 - 500 : Flare spawns, and scales up to it's main size (width parameter) +500 - 1500 : Flare moves up the height/2 of model (height parameter) +1500 - 2000 : Flare scales down and disappears ( width * 1.0 - (timeScale) ) +------------------------- +*/ + +void FX_SPTransporterLensFlares( centity_t* cent, vec3_t headVector, int startTime ) { + refEntity_t flare; + trace_t tr; + int i; + int direction = 1; + int timeOffset = 0; //250 - time between first and second flares appearing; + float ratio; + float dlightRatio; + + vec3_t origin, tempOrigin; + int width; + int height; + + //Hrmm... we have a glitch lol. Since DEPTHHACK is on, the flare will be drawn + //from ANYWHERE IN THE LEVEL! O_o + //So.... uh, we'll do a trace between ourselves and the entity this is attached + //to, and if they can't see each other, screw it. :P + if ( cg.predictedPlayerState.clientNum != cent->currentState.clientNum ) { + CG_Trace( &tr, cg.refdef.vieworg, NULL, NULL, + cent->lerpOrigin, cg.predictedPlayerState.clientNum, CONTENTS_SOLID ); + if ( tr.fraction != 1 ) { + return; + } + } + /*else { + Com_Printf( "Origin: { %f, %f, %f }\n", cent->lerpOrigin[0], cent->lerpOrigin[1], cent->lerpOrigin[2] ); + Com_Printf( "HeadVector: { %f, %f, %f }\n", headVector[0], headVector[1], headVector[2] ); + return; + }*/ + + //calculate the necessary data we need to place the origin in the direct center of the model + + memset( &flare, 0, sizeof( flare ) ); + + //Bah. I thought lerpOrigin was at the base of the feet. Turns out it's at the knees O_o + //This little hack should help that + VectorCopy( cent->lerpOrigin, tempOrigin ); + tempOrigin[2] -= 24; + + //If the distance means we're not lying down + if ( ( headVector[2] - tempOrigin[2] ) > 8 ) { + //find the average between our lerp origin and headVector to find the center + //VectorAdd( headVector, tempOrigin, origin ); + //VectorScale( origin, 0.5, origin ); + VectorAverage( headVector, tempOrigin, origin ); + + width = 30; + height = (headVector[2] - tempOrigin[2]) / 2; + } + else { + width = 30; + height = 4; + + VectorCopy( cent->lerpOrigin, origin); + } + + flare.reType = RT_SPRITE; + flare.shaderRGBA[0] = 0xff; + flare.shaderRGBA[1] = 0xff; + flare.shaderRGBA[2] = 0xff; + flare.shaderRGBA[3] = 0xff; + + flare.data.sprite.rotation = 0; + flare.nonNormalizedAxes = qtrue; //needed for effective scaling + + flare.renderfx |= RF_DEPTHHACK; //DEPTHHACK renders the element over everything else. Useful in this lens flare simulation case :) + + //loop 4 times = 4 flares. :) + for (i = 0; i < 4; i++ ) { + VectorClear( flare.origin ); + VectorCopy( origin, flare.origin); + + //the first two flares are the main ones + if ( i < 2 ) { + flare.customShader = cgs.media.transport1Shader; //1 + timeOffset = startTime; + } + else { // the second two spawn a little later + flare.customShader = cgs.media.transport2Shader; + timeOffset = startTime + 650; //750 + } + + //the second flare each round goes down instead of up + if ( i % 2 == 0) + direction = 1; + else + direction = -1; + + //=========================== + + if ( cg.time > timeOffset + 2000 ) { + continue; + } + + //Phase 1: flares get bigger + if ( cg.time < timeOffset + 500 ) { + ratio = ((float)(cg.time - timeOffset) * 0.002); + if (ratio < 0 ) + ratio = 0.0f; + else if (ratio > 1 ) + ratio = 1.0f; + + flare.data.sprite.radius = (float)width * ratio; + /*if ( i ==0 ) + Com_Printf( "Phase 1 Radius: %f\n", flare.data.sprite.radius );*/ + } + //Phase 2: flares move up/down character + if ( ( cg.time < timeOffset + 1500 ) && ( cg.time >= timeOffset + 500 ) ) { + ratio = ( (float)(cg.time - (timeOffset + 500) ) * 0.001 ); + if (ratio < 0 ) + ratio = 0.0f; + else if (ratio > 1 ) + ratio = 1.0f; + + flare.data.sprite.radius = (float)width; + flare.origin[2] += (float)direction * (float)height * ratio; + /*if (i == 0 ) + Com_Printf( "Phase 2 Location: %f\n", flare.origin[2] );*/ + } + //Phase 3: flares get smaller + if ( ( cg.time < timeOffset + 2000 ) && ( cg.time >= timeOffset + 1500 ) ) { + ratio = 1.0f - ( (float)(cg.time - ( timeOffset + 1500 ) ) * 0,002 ); + if (ratio < 0 ) + ratio = 0.0f; + else if (ratio > 1 ) + ratio = 1.0f; + + flare.origin[2] += ((float)height * (float)direction); + flare.data.sprite.radius = (float)width * ratio; + /*if ( i == 0 ) + Com_Printf( "Phase 3 Radius: %f\n", flare.data.sprite.radius );*/ + } + + trap_R_AddRefEntityToScene( &flare ); + } + + //dynamic light calculation + if ( cg.time < ( startTime + 2000 ) ) { + dlightRatio = (float)( cg.time - startTime ) * 0,0005; + } + else { + dlightRatio = 1.0f - ( (float)( cg.time - ( startTime + 2000 ) ) * 0,0005 ); + } + + //dynamic light FX + trap_R_AddLightToScene( origin, 80.0f * dlightRatio, 0.345, 0.624, 0.835 ); +} + +/* +------------------------- +TransporterParticle +------------------------- +*/ + +qboolean TransporterParticle( localEntity_t *le) +{ + vec3_t org, velocity = { 0, 0, 68 }; + vec3_t accel = { 0, 0, -12 }; + float scale, dscale; + qhandle_t shader; + + VectorCopy( le->refEntity.origin, org ); + org[2] += 0;//38; + + shader = ( le->data.spawner.dir[0] == 0 ) ? cgs.media.trans1Shader : cgs.media.trans2Shader; + scale = ( le->data.spawner.dir[0] == 0 ) ? 2.0 : 4.0; + dscale = ( le->data.spawner.dir[0] == 0 ) ? 4.0 : 24.0; + + le->data.spawner.dir[0]++; + + FX_AddSprite( org, + velocity, + qfalse, + scale, + dscale, + 1.0f, + 0.0f, + 0, + 0.0f, + 450.0f, + shader ); + + VectorScale( velocity, -1, velocity ); + VectorScale( accel, -1, accel ); + + FX_AddSprite( org, + velocity, + qfalse, + scale, + dscale, + 1.0f, + 0.0f, + 0, + 0.0f, + 450.0f, + shader ); + + return qtrue; +} + +/* +------------------------- +TransporterPad +------------------------- +*/ + +qboolean TransporterPad( localEntity_t *le) +{ + vec3_t org; + vec3_t up = {0,0,1}; + float scale, dscale; + qhandle_t shader; + + VectorCopy( le->refEntity.origin, org ); + org[2] -= 3; + + shader = cgs.media.trans1Shader; + scale = 20.0; + dscale = 2.0; + + FX_AddQuad( org, + up, + scale, + dscale, + 1.0f, + 0.0f, + 0, + 950.0f, + shader ); + return qtrue; +} + +/* +------------------------- +FX_Transporter +------------------------- +*/ + +void FX_Transporter( vec3_t origin ) +{ + vec3_t up = {0,0,1}; + + FX_AddSpawner( origin, up, NULL, NULL, qfalse, 0, 0, 200, TransporterParticle, 0 ); +// trap_S_StartSound( origin, NULL, CHAN_AUTO, cgs.media.teleInSound ); +} + +/* +------------------------- +FX_TransporterPad +------------------------- +*/ + +void FX_TransporterPad( vec3_t origin ) +{ + vec3_t up = {0,0,1}; + + FX_AddSpawner( origin, up, NULL, NULL, qfalse, 1000, 0, 0, TransporterPad, 0 ); +} + diff --git a/cgame/tr_types.h b/cgame/tr_types.h new file mode 100644 index 0000000..d74ac99 --- /dev/null +++ b/cgame/tr_types.h @@ -0,0 +1,256 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +#ifndef __TR_TYPES_H +#define __TR_TYPES_H + + +#define MAX_DLIGHTS 32 // can't be increased, because bit flags are used on surfaces + +#define MAX_ENTITIES 1023 // can't be increased without changing drawsurf bit packing + + +// renderfx flags +#define RF_LOWLIGHT 0x0001 // subtract ambient to keep it in the dark +#define RF_THIRD_PERSON 0x0002 // don't draw through eyes, only mirrors (player bodies, chat sprites) +#define RF_FIRST_PERSON 0x0004 // only draw through eyes (view weapon, damage blood blob) +#define RF_DEPTHHACK 0x0008 // for view weapon Z crunching +#define RF_FULLBRIGHT 0x0010 // Render with a bright ambient light. +#define RF_NOSHADOW 0x0040 // don't add stencil shadows + +#define RF_LIGHTING_ORIGIN 0x0080 // 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 0x0100 // use refEntity->shadowPlane +#define RF_WRAP_FRAMES 0x0200 // mod the model frames by the maxframes to allow continuous + // animation without needing to know the frame count +#define RF_CAP_FRAMES 0x0400 // cap the model frames by the maxframes for one shot anims +#define RF_FORCE_ENT_ALPHA 0x0800 // Models should use ent alpha regardless of what the shader says. + + +// refdef flags +#define RDF_NOWORLDMODEL 1 // used for player configuration screen +#define RDF_HYPERSPACE 4 // teleportation effect + +#ifdef XTRA +#define RDF_MOTIONBLUR 8 +#endif + +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_SPRITE, + RT_ORIENTEDSPRITE, // Replaces RT_POLY, which wasn't used. --Pat + RT_ALPHAVERTPOLY, // Individual alpha levels on each vertex + RT_BEAM, + RT_RAIL_CORE, + RT_RAIL_RINGS, + RT_LIGHTNING, + RT_PORTALSURFACE, // doesn't draw anything, just info for portals + RT_LINE, // New type for Trek MP --Pat + RT_ORIENTEDLINE, + RT_LINE2, // New line type for Trek MP, with taper support --Pat + RT_BEZIER, // what he said --keith + RT_CYLINDER, // Yet another Trek primitive! + RT_ELECTRICITY, // Yet another Trek primitive! + + RT_MAX_REF_ENTITY_TYPE +} refEntityType_t; + + +typedef struct { + refEntityType_t reType; + int renderfx; + + qhandle_t hModel; // opaque type outside refresh + + // most recent data + vec3_t lightingOrigin; // so multi-part models can be lit identically (RF_LIGHTING_ORIGIN) + float shadowPlane; // projection shadows go here, stencils go slightly lower + + vec3_t axis[3]; // rotation vectors + qboolean nonNormalizedAxes; // axis are not normalized, i.e. they have scale + float origin[3]; // also used as MODEL_BEAM's "from" + int frame; // also used as MODEL_BEAM's diameter + + // previous data for frame interpolation + float oldorigin[3]; // also used as MODEL_BEAM's "to" + int oldframe; + float backlerp; // 0.0 = current, 1.0 = old + + // texturing + int skinNum; // inline skin index + qhandle_t customSkin; // NULL for default skin + qhandle_t customShader; // use one image for the entire thing + + // misc + byte shaderRGBA[4]; // colors used by rgbgen entity shaders + float shaderTexCoord[2]; // texture coordinates used by tcMod entity modifiers + float shaderTime; // subtracted from refdef time to control effect start times + + // extra sprite information + 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 + // TiM: You're a fecal. I prefer that style :D + // TiM: (c)2008 You're a you're a fecal. ;P I've grown to like the other one now.... it's way easier to read XD + { + 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; +} refEntity_t; + + +#define MAX_RENDER_STRINGS 8 +#define MAX_RENDER_STRING_LENGTH 32 + +typedef struct { + int x, y, width, height; + float fov_x, fov_y; + vec3_t vieworg; + vec3_t viewaxis[3]; // transformation matrix + + // time in milliseconds for shader effects and other time dependent rendering issues + int time; + + int rdflags; // RDF_NOWORLDMODEL, etc + + // 1 bits will prevent the associated area from rendering at all + byte areamask[MAX_MAP_AREA_BYTES]; + + // text messages for deform text shaders +// char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH]; +} refdef_t; + + +typedef enum { + STEREO_CENTER, + STEREO_LEFT, + STEREO_RIGHT +} stereoFrame_t; + + +/* +** glconfig_t +** +** Contains variables specific to the OpenGL configuration +** being run right now. These are constant once the OpenGL +** subsystem is initialized. +*/ +typedef enum { + TC_NONE, + TC_S3TC, + TC_S3TC_DXT +} textureCompression_t; + +typedef enum { + GLDRV_ICD, // driver is integrated with window system + // WARNING: there are tests that check for + // > GLDRV_ICD for minidriverness, so this + // should always be the lowest value in this + // enum set + GLDRV_STANDALONE, // driver is a non-3Dfx standalone driver + GLDRV_VOODOO // driver is a 3Dfx standalone driver +} glDriverType_t; + +typedef enum { + GLHW_GENERIC, // where everthing works the way it should + GLHW_3DFX_2D3D, // Voodoo Banshee or Voodoo3, relevant since if this is + // the hardware type then there can NOT exist a secondary + // display adapter + GLHW_RIVA128, // where you can't interpolate alpha + GLHW_RAGEPRO, // where you can't modulate alpha on alpha textures + GLHW_PERMEDIA2 // where you don't have src*dst +} glHardwareType_t; + +typedef struct { + char renderer_string[MAX_STRING_CHARS]; + char vendor_string[MAX_STRING_CHARS]; + char version_string[MAX_STRING_CHARS]; + char extensions_string[2*MAX_STRING_CHARS]; + + int maxTextureSize; // queried from GL + int maxActiveTextures; // multitexture ability + + int colorBits, depthBits, stencilBits; + + glDriverType_t driverType; + glHardwareType_t hardwareType; + + qboolean deviceSupportsGamma; + textureCompression_t textureCompression; + qboolean textureEnvAddAvailable; + qboolean textureFilterAnisotropicAvailable; + + 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; + + +#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/codetidbits.txt b/codetidbits.txt new file mode 100644 index 0000000..d71213e --- /dev/null +++ b/codetidbits.txt @@ -0,0 +1,98 @@ +/* Old Code bits + hcolor[0] = 0.0; + hcolor[1] = 1.0; + hcolor[2] = 0.0; + hcolor[3] = 1.0; + + x = 250; + y2 = 200; + w = 100; + h = 100; + glowOffset = 60; + streakW = 500; + streakH = 10;*/ + + //reflecAnamorphic lenses make their flares really stretched + //Actually, just make this control the reflections. + //The user can define the w + h themselves. :P + /*if ( reflecAnamorphic ) { + w = w * 2; + //widthDouble = 2; + }*/ + + //else + /*CG_DrawPic( + ( ( xCart/lensReflec[i].offset )+HALF_SCREEN_WIDTH ) - ( reflecAnamorphic ? lensReflec[i].width : lensReflec[i].width/2 ), //X + ( ( yCart/lensReflec[i].offset )+HALF_SCREEN_HEIGHT ) - ( lensReflec[i].height/2 ), //Y + reflecAnamorphic ? lensReflec[i].width * 2 : lensReflec[i].width, //W + lensReflec[i].height, //H + lensReflec[i].graphic //pic + );*/ +/* float yMid = ymin + ( (ymax - ymin) /2 ); + + //if the flare's origin moves to out of range, fade out the alpha on the reflections + //this is how it works O_o each side of the screen is a different quadrant controlled + //by a dif equation, excluding the corners where they'd overlap. The corners have their own + //equations applied later on, hopefully forming a complte circle for the flares to fade out easily. :S + + + //left bar + if ( x > xmin && x < xmax && y < ymin && y > -ymin ) + reflecAlpha = (float)(( xmax - (float)x ) / xDif); + //right bar + if ( x < -xmin && x > -xmax && y < ymin && y > -ymin ) + reflecAlpha = (float)(( xmax + (float)x ) / xDif); + //upper bar + if ( y > ymin && y < ymax && x < xmin && x > -xmin ) + reflecAlpha = (float)(( ymax - (float)y ) / yDif); + //lower bar + if ( y < -ymin && y > -ymax && x < xmin && x > -xmin ) + reflecAlpha = (float)(( ymax + (float)y ) / yDif); + + //lower right quadrant +/* if ( x > xmin && x < xmax && y > ymin && y < ymax ) { + if ( (x*0.75) > yMid )*/ + +/* + CG_DrawPic( , y - ((h * hazeOffset) * 0.5), (w * hazeOffset), h * hazeOffset, cgs.media.flareHaze ); //Surrounding ambient haze + + trap_R_SetColor( strkColor ); + CG_DrawPic( streakX , streakY , streakW, streakH, cgs.media.flareStreak ); //Colored portion of the anamorphic streaks + + trap_R_SetColor( color ); + CG_DrawPic( x - ((w * glowOffset) * 0.5), y - ((h * glowOffset) * 0.5), (w * glowOffset), h * glowOffset, cgs.media.flareCore ); //Main colored glow bit of the main flare + + if ( whiteStreaks ) { //if player wanted white streaks in their streaks + strkColor[0] = strkColor[1] = strkColor[2] = 1.0; + + trap_R_SetColor( strkColor ); //White + CG_DrawPic( streakX + (streakW*0.2), streakY + (streakH*0.2), streakW*0.6, streakH*0.6, cgs.media.flareStreak ); //White Core of streak is ALWAYS 20% smaller. + } + + trap_R_SetColor( NULL ); + CG_DrawPic( x, y, w, h, cgs.media.flareCore ); //Draw teh main fl4r3 :)*/ + + //w = (0.0001 * -(length*length)) + w; + //h = (0.0001 * -(length*length)) + h; + + /*if ( !clamp ) { + w = w + (int)(-0.1 * length ); + h = h + (int)(-0.1 * length ); + + //clamp it at 10% then fade it out + if ( ((float)w/(float)w1) <= 0.2 && ((float)h/(float)h1) <= 0.2 ) { + w = w1*0.2f; + h = h1*0.2f; + } + + //then fade it back in + //if ( ((float)w/(float)w1) >= .05 && ((float)h/(float)h1) >= .05 && fadeAlpha == 0.0 ) + + if ( ((float)w/(float)w1) <= 0.15 && ((float)h/(float)h1) <= 0.15 ) { + w = w1 * 0.15; + h = h1 * 0.15; + } + //CG_Printf( "Distance = %f, w = %i, h = %i\n", length, w, h); + }*/ + +//------------------------------------------------------ \ No newline at end of file diff --git a/game/Makefile b/game/Makefile new file mode 100644 index 0000000..7895451 --- /dev/null +++ b/game/Makefile @@ -0,0 +1,220 @@ +default: so +so: build_so + +# determine arch and platform +ARCH=$(shell uname -m | sed -e s/i.86/i386/) +PLATFORM=$(shell uname|sed -e s/_.*//|tr '[:upper:]' '[:lower:]') + +# compiler to use for building shared objects +CC = gcc + +# cflags for the compiler +ifeq ($(PLATFORM), mingw32) +SOCFLAGS = $(CFLAGS) +LIBMYSQL = +else +SOCFLAGS = $(CFLAGS) -fPIC +LIBMYSQL = -mysqlclient +endif + +# set extension +ifeq ($(PLATFORM), mingw32) +EXT=dll +ARCH=x86 +else +EXT=so +endif + +# game objects +OBJ = \ + g_ui.o \ + g_lua.o \ + q_shared.o \ + q_math.o \ + g_weapon.o \ + g_utils.o \ + g_usable.o \ + g_turrets.o \ + g_trigger.o \ + g_team.o \ + g_target.o \ + g_svcmds.o \ + g_spawn.o \ + g_session.o \ + g_mover.o \ + g_missile.o \ + g_misc.o \ + g_mem.o \ + g_main.o \ + g_log.o \ + g_items.o \ + g_fx.o \ + g_combat.o \ + g_cmds.o \ + g_client.o \ + g_breakable.o \ + g_bot.o \ + g_arenas.o \ + g_active.o \ + bg_slidemove.o \ + bg_pmove.o \ + bg_oums.o \ + bg_misc.o \ + ai_team.o \ + ai_main.o \ + ai_dmq3.o \ + ai_dmnet.o \ + ai_cmd.o \ + ai_chat.o \ + lua_game.o \ + lua_entity.o \ + lua_vector.o \ + lua_mover.o \ + lua_qmath.o + +# game object for syscalls to the engine +SOOBJ = \ + g_syscalls.o + +# objects for lua +LUAOBJ = \ + lapi.o \ + lauxlib.o \ + lbaselib.o \ + lbitlib.o \ + lcode.o \ + lcorolib.o \ + lctype.o \ + ldblib.o \ + ldebug.o \ + ldo.o \ + ldump.o \ + lfunc.o \ + lgc.o \ + linit.o \ + liolib.o \ + llex.o \ + lmathlib.o \ + lmem.o \ + loadlib.o \ + lobject.o \ + lopcodes.o \ + loslib.o \ + lparser.o \ + lstate.o \ + lstring.o \ + lstrlib.o \ + ltable.o \ + ltablib.o \ + ltm.o \ + lua.o \ + luac.o \ + lundump.o \ + lvm.o \ + lzio.o + +# do cc for shared library +DO_SOCC = $(CC) $(SOCFLAGS) -o $@ -c $< +# do cc for lua +DO_LUACC = $(CC) -O2 -Wall -fPIC -DLUA_COMPAT_ALL -o $@ -c $< + +build_so: DO_CC=$(DO_SOCC) + +# game +ai_chat.o : ai_chat.c; $(DO_CC) +ai_cmd.o : ai_cmd.c; $(DO_CC) +ai_dmnet.o : ai_dmnet.c; $(DO_CC) +ai_dmq3.o : ai_dmq3.c; $(DO_CC) +ai_main.o : ai_main.c; $(DO_CC) +ai_team.o : ai_team.c; $(DO_CC) +bg_misc.o : bg_misc.c; $(DO_CC) +bg_pmove.o : bg_pmove.c; $(DO_CC) +bg_slidemove.o : bg_slidemove.c; $(DO_CC) +g_active.o : g_active.c; $(DO_CC) +g_arenas.o : g_arenas.c; $(DO_CC) +g_bot.o : g_bot.c; $(DO_CC) +g_breakable.o : g_breakable.c; $(DO_CC) +g_client.o : g_client.c; $(DO_CC) +g_cmds.o : g_cmds.c; $(DO_CC) +g_combat.o : g_combat.c; $(DO_CC) +g_fx.o : g_fx.c; $(DO_CC) +g_items.o : g_items.c; $(DO_CC) +g_log.o : g_log.c; $(DO_CC) +g_main.o : g_main.c; $(DO_CC) +g_mem.o : g_mem.c; $(DO_CC) +g_misc.o : g_misc.c; $(DO_CC) +g_missile.o : g_missile.c; $(DO_CC) +g_mover.o : g_mover.c; $(DO_CC) +g_session.o : g_session.c; $(DO_CC) +g_spawn.o : g_spawn.c; $(DO_CC) +g_svcmds.o : g_svcmds.c; $(DO_CC) +g_target.o : g_target.c; $(DO_CC) +g_team.o : g_team.c; $(DO_CC) +g_trigger.o : g_trigger.c; $(DO_CC) +g_turrets.o : g_turrets.c; $(DO_CC) +g_usable.o : g_usable.c; $(DO_CC) +g_utils.o : g_utils.c; $(DO_CC) +g_weapon.o : g_weapon.c; $(DO_CC) +q_math.o : q_math.c; $(DO_CC) +q_shared.o : q_shared.c; $(DO_CC) +g_lua.o: g_lua.c; $(DO_CC) +g_ui.o: g_ui.c; $(DO_CC) +g_sql.o: g_sql.c; $(DO_CC) +bg_oums.o : bg_oums.c; $(DO_CC) +lua_game.o: lua_game.c; $(DO_CC) +lua_entity.o: lua_entity.c; $(DO_CC) +lua_mover.o: lua_mover.c; $(DO_CC) +lua_qmath.o: lua_qmath.c; $(DO_CC) +lua_vector.o: lua_vector.c; $(DO_CC) + +# game syscalls +g_syscalls.o : g_syscalls.c; $(DO_CC) + +# bg_lib +bg_lib.o : bg_lib.c; $(DO_CC) + +# lua +lapi.o: lapi.c; $(DO_LUACC) +lauxlib.o: lauxlib.c; $(DO_LUACC) +lbaselib.o: lbaselib.c; $(DO_LUACC) +lbitlib.o: lbitlib.c; $(DO_LUACC) +lcode.o: lcode.c; $(DO_LUACC) +lcorolib.o: lcorolib.c; $(DO_LUACC) +lctype.o: lctype.c; $(DO_LUACC) +ldblib.o: ldblib.c; $(DO_LUACC) +ldebug.o: ldebug.c; $(DO_LUACC) +ldo.o: ldo.c; $(DO_LUACC) +ldump.o: ldump.c; $(DO_LUACC) +lfunc.o: lfunc.c; $(DO_LUACC) +lgc.o: lgc.c; $(DO_LUACC) +linit.o: linit.c; $(DO_LUACC) +liolib.o: liolib.c; $(DO_LUACC) +llex.o: llex.c; $(DO_LUACC) +lmathlib.o: lmathlib.c; $(DO_LUACC) +lmem.o: lmem.c; $(DO_LUACC) +loadlib.o: loadlib.c; $(DO_LUACC) +lobject.o: lobject.c; $(DO_LUACC) +lopcodes.o: lopcodes.c; $(DO_LUACC) +loslib.o: loslib.c; $(DO_LUACC) +lparser.o: lparser.c; $(DO_LUACC) +lstate.o: lstate.c; $(DO_LUACC) +lstring.o: lstring.c; $(DO_LUACC) +lstrlib.o: lstrlib.c; $(DO_LUACC) +ltable.o: ltable.c; $(DO_LUACC) +ltablib.o: ltablib.c; $(DO_LUACC) +ltm.o: ltm.c; $(DO_LUACC) +lua.o: lua.c; $(DO_LUACC) +luac.o: luac.c; $(DO_LUACC) +lundump.o: lundump.c; $(DO_LUACC) +lvm.o: lvm.c; $(DO_LUACC) +lzio.o: lzio.c; $(DO_LUACC) + +build_so: $(OBJ) $(SOOBJ) $(LUAOBJ) +ifeq ($(PLATFORM), mingw32) + $(CC) -shared -W1,--export-all-symbols,-soname,qqgame$(ARCH).$(EXT) -o qagame$(ARCH).$(EXT) $(OBJ) $(SOOBJ) $(LUAOBJ) ../mysql/libs/mysqlclient.lib -lm +else + $(CC) -shared -Wl,--export-dynamic,-soname,qagame$(ARCH).$(EXT) -o qagame$(ARCH).$(EXT) $(OBJ) $(SOOBJ) $(LUAOBJ) $(LIBMYSQL) -lm +endif + +clean: + rm -f *.o *.$(EXT) diff --git a/game/ai_chat.c b/game/ai_chat.c new file mode 100644 index 0000000..3ea3160 --- /dev/null +++ b/game/ai_chat.c @@ -0,0 +1,1161 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_chat.c + * + * desc: Quake3 bot AI + * + * $Archive: /StarTrek/Code-DM/game/ai_chat.c $ + * $Author: Mgummelt $ + * $Revision: 7 $ + * $Modtime: 3/09/01 11:52a $ + * $Date: 3/09/01 12:02p $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + + +/* +================== +BotNumActivePlayers +================== +*/ +int BotNumActivePlayers(void) { + int i, num; + char buf[MAX_INFO_STRING]; + static int maxclis; + + if (!maxclis) + maxclis = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + num = 0; + for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + num++; + } + return num; +} + +/* +================== +BotIsFirstInRankings +================== +*/ +int BotIsFirstInRankings(bot_state_t *bs) { + int i, score; + char buf[MAX_INFO_STRING]; + static int maxclis; + playerState_t ps; + + if (!maxclis) + maxclis = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + score = bs->cur_ps.persistant[PERS_SCORE]; + for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + BotAI_GetClientState(i, &ps); + if (score < ps.persistant[PERS_SCORE]) return qfalse; + } + return qtrue; +} + +/* +================== +BotIsLastInRankings +================== +*/ +int BotIsLastInRankings(bot_state_t *bs) { + int i, score; + char buf[MAX_INFO_STRING]; + static int maxclis; + playerState_t ps; + + if (!maxclis) + maxclis = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + score = bs->cur_ps.persistant[PERS_SCORE]; + for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + BotAI_GetClientState(i, &ps); + if (score > ps.persistant[PERS_SCORE]) return qfalse; + } + return qtrue; +} + +/* +================== +BotFirstClientInRankings +================== +*/ +char *BotFirstClientInRankings(void) { + int i, bestscore, bestclient; + char buf[MAX_INFO_STRING]; + static char name[32]; + static int maxclis; + playerState_t ps; + + if (!maxclis) + maxclis = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + bestscore = -999999; + bestclient = 0; + for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + BotAI_GetClientState(i, &ps); + if (ps.persistant[PERS_SCORE] > bestscore) { + bestscore = ps.persistant[PERS_SCORE]; + bestclient = i; + } + } + EasyClientName(bestclient, name, 32); + return name; +} + +/* +================== +BotLastClientInRankings +================== +*/ +char *BotLastClientInRankings(void) { + int i, worstscore, bestclient; + char buf[MAX_INFO_STRING]; + static char name[32]; + static int maxclis; + playerState_t ps; + + if (!maxclis) + maxclis = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + worstscore = 999999; + bestclient = 0; + for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + BotAI_GetClientState(i, &ps); + if (ps.persistant[PERS_SCORE] < worstscore) { + worstscore = ps.persistant[PERS_SCORE]; + bestclient = i; + } + } + EasyClientName(bestclient, name, 32); + return name; +} + +/* +================== +BotRandomOpponentName +================== +*/ +char *BotRandomOpponentName(bot_state_t *bs) { + int i, count; + char buf[MAX_INFO_STRING]; + int opponents[MAX_CLIENTS], numopponents; + static int maxclis; + static char name[32]; + + if (!maxclis) + maxclis = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + numopponents = 0; + opponents[0] = 0; + for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) { + if (i == bs->client) continue; + // + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + //skip team mates + if (BotSameTeam(bs, i)) continue; + // + opponents[numopponents] = i; + numopponents++; + } + count = random() * numopponents; + for (i = 0; i < numopponents; i++) { + count--; + if (count <= 0) { + EasyClientName(opponents[i], name, sizeof(name)); + return name; + } + } + EasyClientName(opponents[0], name, sizeof(name)); + return name; +} + +/* +================== +BotMapTitle +================== +*/ + +char *BotMapTitle(void) { + char info[1024]; + static char mapname[128]; + + trap_GetServerinfo(info, sizeof(info)); + + strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1); + mapname[sizeof(mapname)-1] = '\0'; + + return mapname; +} + + +/* +================== +BotWeaponNameForMeansOfDeath +================== +*/ + +char *BotWeaponNameForMeansOfDeath(int mod) { + switch(mod) { + case MOD_PHASER: + case MOD_PHASER_ALT: return "Phaser"; + case MOD_CRIFLE: + case MOD_CRIFLE_SPLASH: + case MOD_CRIFLE_ALT: + case MOD_CRIFLE_ALT_SPLASH: return "Compression Rifle"; + case MOD_IMOD: + case MOD_IMOD_ALT: return "Infinity Modulator"; + case MOD_SCAVENGER: + case MOD_SCAVENGER_ALT: + case MOD_SCAVENGER_ALT_SPLASH: return "Scavenger Rifle"; + case MOD_STASIS: + case MOD_STASIS_ALT: return "Stasis Weapon"; + case MOD_GRENADE: + case MOD_GRENADE_SPLASH: + case MOD_GRENADE_ALT_SPLASH: return "Grenade Launcher"; + case MOD_TETRION: + case MOD_TETRION_ALT: return "Tetryon Disruptor"; + case MOD_DREADNOUGHT: + case MOD_DREADNOUGHT_ALT: return "Arc Welder"; + case MOD_QUANTUM: + case MOD_QUANTUM_SPLASH: + case MOD_QUANTUM_ALT: + case MOD_QUANTUM_ALT_SPLASH: return "Photon Burst Cannon"; + case MOD_KNOCKOUT: return "Hypo"; + + default: return "[unknown weapon]"; + } +} + +/* +================== +BotRandomWeaponName +================== +*/ +char *BotRandomWeaponName(void) { + int rnd; + + rnd = random() * 8.9; + switch(rnd) { + case 0: return "Phaser"; + case 1: return "Compression Rifle"; + case 2: return "Infinity Modulator"; + case 3: return "Scavenger Rifle"; + case 4: return "Stasis Weapon"; + case 5: return "Grenade Launcher"; + case 6: return "Tetryon Disruptor"; + case 7: return "Dermal Regenerator"; + default: return "Photon Burst Cannon"; + } +} + +/* +================== +BotVisibleEnemies +================== +*/ +int BotVisibleEnemies(bot_state_t *bs) { + float vis, dist; + int i; + vec3_t dir; + aas_entityinfo_t entinfo; + + for (i = 0; i < MAX_CLIENTS; i++) { + + if (i == bs->client) continue; + // + BotEntityInfo(i, &entinfo); + // + if (!entinfo.valid) continue; + //if the enemy isn't dead and the enemy isn't the bot self + if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; + //if the enemy is invisible and not shooting + if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { + continue; + } + //calculate the distance towards the enemy + VectorSubtract(entinfo.origin, bs->origin, dir); + dist = VectorLength(dir); + //if on the same team + if (BotSameTeam(bs, i)) continue; + //check if the enemy is visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis > 0) return qtrue; + } + return qfalse; +} + +/* +================== +BotValidChatPosition +================== +*/ +int BotValidChatPosition(bot_state_t *bs) { + vec3_t point, start, end, mins, maxs; + bsp_trace_t trace; + + //if the bot is dead all positions are valid + if (BotIsDead(bs)) return qtrue; + //never start chatting with a powerup + if (bs->inventory[INVENTORY_QUAD] || + bs->inventory[INVENTORY_HASTE] || + bs->inventory[INVENTORY_INVISIBILITY] || + bs->inventory[INVENTORY_REGEN] || + bs->inventory[INVENTORY_FLIGHT]) return qfalse; + //must be on the ground + //if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) return qfalse; + //do not chat if in lava or slime + VectorCopy(bs->origin, point); + point[2] -= 24; + if (trap_PointContents(point,bs->entitynum) & (CONTENTS_LAVA|CONTENTS_SLIME)) return qfalse; + //do not chat if under water + VectorCopy(bs->origin, point); + point[2] += 32; + if (trap_PointContents(point,bs->entitynum) & MASK_WATER) return qfalse; + //must be standing on the world entity + VectorCopy(bs->origin, start); + VectorCopy(bs->origin, end); + start[2] += 1; + end[2] -= 10; + trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, mins, maxs); + BotAI_Trace(&trace, start, mins, maxs, end, bs->client, MASK_SOLID); + if (trace.ent != ENTITYNUM_WORLD) return qfalse; + //the bot is in a position where it can chat + return qtrue; +} + +/* +================== +BotChat_EnterGame +================== +*/ +int BotChat_EnterGame(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > trap_AAS_Time() - 3) return qfalse; + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + rnd = 0; + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + if (!BotValidChatPosition(bs)) return qfalse; + BotAI_BotInitialChat(bs, "game_enter", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL); + bs->lastchat_time = trap_AAS_Time(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_ExitGame +================== +*/ +int BotChat_ExitGame(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > trap_AAS_Time() - 3) return qfalse; + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + rnd = 0; + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + // + BotAI_BotInitialChat(bs, "game_exit", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL); + bs->lastchat_time = trap_AAS_Time(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_StartLevel +================== +*/ +int BotChat_StartLevel(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (BotIsObserver(bs)) return qfalse; + if (bs->lastchat_time > trap_AAS_Time() - 3) return qfalse; + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + rnd = 0; + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + BotAI_BotInitialChat(bs, "level_start", + EasyClientName(bs->client, name, 32), // 0 + NULL); + bs->lastchat_time = trap_AAS_Time(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_EndLevel +================== +*/ +int BotChat_EndLevel(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (BotIsObserver(bs)) return qfalse; + if (bs->lastchat_time > trap_AAS_Time() - 3) return qfalse; + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + rnd = 0; + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + // + if (BotIsFirstInRankings(bs)) { + BotAI_BotInitialChat(bs, "level_end_victory", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL); + } + else if (BotIsLastInRankings(bs)) { + BotAI_BotInitialChat(bs, "level_end_lose", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL); + } + else { + BotAI_BotInitialChat(bs, "level_end", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL); + } + bs->lastchat_time = trap_AAS_Time(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_Death +================== +*/ +int BotChat_Death(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > trap_AAS_Time() - 3) return qfalse; + rnd = 0; + //if fast chatting is off + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + // + if (bs->lastkilledby >= 0 && bs->lastkilledby < MAX_CLIENTS) + EasyClientName(bs->lastkilledby, name, 32); + else + strcpy(name, "[world]"); + // + if (TeamPlayIsOn() && BotSameTeam(bs, bs->lastkilledby)) { + if (bs->lastkilledby == bs->client) return qfalse; + BotAI_BotInitialChat(bs, "death_teammate", name, NULL); + bs->chatto = CHAT_TEAM; + } + else + { + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // + if (bs->botdeathtype == MOD_WATER) + BotAI_BotInitialChat(bs, "death_drown", BotRandomOpponentName(bs), NULL); + else if (bs->botdeathtype == MOD_SLIME) + BotAI_BotInitialChat(bs, "death_slime", BotRandomOpponentName(bs), NULL); + else if (bs->botdeathtype == MOD_LAVA) + BotAI_BotInitialChat(bs, "death_lava", BotRandomOpponentName(bs), NULL); + else if (bs->botdeathtype == MOD_FALLING) + BotAI_BotInitialChat(bs, "death_cratered", BotRandomOpponentName(bs), NULL); + else if (bs->botsuicide || //all other suicides by own weapon + bs->botdeathtype == MOD_CRUSH || + bs->botdeathtype == MOD_SUICIDE || + bs->botdeathtype == MOD_RESPAWN || + bs->botdeathtype == MOD_TARGET_LASER || + bs->botdeathtype == MOD_TRIGGER_HURT || + bs->botdeathtype == MOD_UNKNOWN || + bs->botdeathtype == MOD_EXPLOSION) + BotAI_BotInitialChat(bs, "death_suicide", BotRandomOpponentName(bs), NULL); + else if (bs->botdeathtype == MOD_TELEFRAG) + BotAI_BotInitialChat(bs, "death_telefrag", name, NULL); + else { +#if 0 + if ((bs->botdeathtype == MOD_GAUNTLET || + bs->botdeathtype == MOD_RAILGUN || + bs->botdeathtype == MOD_BFG || + bs->botdeathtype == MOD_BFG_SPLASH) && random() < 0.5) { + + if (bs->botdeathtype == MOD_GAUNTLET) + BotAI_BotInitialChat(bs, "death_gauntlet", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + else if (bs->botdeathtype == MOD_RAILGUN) + BotAI_BotInitialChat(bs, "death_rail", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + else + BotAI_BotInitialChat(bs, "death_bfg", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + } + //choose between insult and praise + else +#endif //0 + + if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1)) { + BotAI_BotInitialChat(bs, "death_insult", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + } + else { + BotAI_BotInitialChat(bs, "death_praise", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + } + } + bs->chatto = CHAT_ALL; + } + bs->lastchat_time = trap_AAS_Time(); + return qtrue; +} + +/* +================== +BotChat_Kill +================== +*/ +int BotChat_Kill(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > trap_AAS_Time() - 3) return qfalse; + rnd = 0; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (bs->lastkilledplayer == bs->client) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + EasyClientName(bs->lastkilledplayer, name, 32); + // + bs->chatto = CHAT_ALL; + if (TeamPlayIsOn() && BotSameTeam(bs, bs->lastkilledplayer)) { + BotAI_BotInitialChat(bs, "kill_teammate", name, NULL); + bs->chatto = CHAT_TEAM; + } + else + { + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // +#if 0 + if (bs->enemydeathtype == MOD_GAUNTLET) { + BotAI_BotInitialChat(bs, "kill_gauntlet", name, NULL); + } + else if (bs->enemydeathtype == MOD_RAILGUN) { + BotAI_BotInitialChat(bs, "kill_rail", name, NULL); + } +#endif // 0 + + else if (bs->enemydeathtype == MOD_TELEFRAG) { + BotAI_BotInitialChat(bs, "kill_telefrag", name, NULL); + } + //choose between insult and praise + else if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1)) { + BotAI_BotInitialChat(bs, "kill_insult", name, NULL); + } + else { + BotAI_BotInitialChat(bs, "kill_praise", name, NULL); + } + } + bs->lastchat_time = trap_AAS_Time(); + return qtrue; +} + +/* +================== +BotChat_EnemySuicide +================== +*/ +int BotChat_EnemySuicide(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > trap_AAS_Time() - 3) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + // + rnd = 0; + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + if (bs->enemy >= 0) EasyClientName(bs->enemy, name, 32); + else strcpy(name, ""); + BotAI_BotInitialChat(bs, "enemy_suicide", name, NULL); + bs->lastchat_time = trap_AAS_Time(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_HitTalking +================== +*/ +int BotChat_HitTalking(bot_state_t *bs) { + char name[32], *weap; + int lasthurt_client; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > trap_AAS_Time() - 3) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + lasthurt_client = g_entities[bs->client].client->lasthurt_client; + if (!lasthurt_client) return qfalse; + if (lasthurt_client == bs->client) return qfalse; + // + if (lasthurt_client < 0 || lasthurt_client >= MAX_CLIENTS) return qfalse; + // + rnd = 0; + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd * 0.5) return qfalse; + } + if (!BotValidChatPosition(bs)) return qfalse; + // + ClientName(g_entities[bs->client].client->lasthurt_client, name, sizeof(name)); + weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_client); + // + BotAI_BotInitialChat(bs, "hit_talking", name, weap, NULL); + bs->lastchat_time = trap_AAS_Time(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_HitNoDeath +================== +*/ +int BotChat_HitNoDeath(bot_state_t *bs) { + char name[32], *weap; + float rnd; + int lasthurt_client; + aas_entityinfo_t entinfo; + + lasthurt_client = g_entities[bs->client].client->lasthurt_client; + if (!lasthurt_client) return qfalse; + if (lasthurt_client == bs->client) return qfalse; + // + if (lasthurt_client < 0 || lasthurt_client >= MAX_CLIENTS) return qfalse; + // + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > trap_AAS_Time() - 3) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + rnd = 0; + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd * 0.5) return qfalse; + } + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + BotEntityInfo(bs->enemy, &entinfo); + if (EntityIsShooting(&entinfo)) return qfalse; + // + ClientName(lasthurt_client, name, sizeof(name)); + weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_mod); + // + BotAI_BotInitialChat(bs, "hit_nodeath", name, weap, NULL); + bs->lastchat_time = trap_AAS_Time(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_HitNoKill +================== +*/ +int BotChat_HitNoKill(bot_state_t *bs) { + char name[32], *weap; + float rnd; + aas_entityinfo_t entinfo; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > trap_AAS_Time() - 3) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + rnd = 0; + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd * 0.5) return qfalse; + } + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + BotEntityInfo(bs->enemy, &entinfo); + if (EntityIsShooting(&entinfo)) return qfalse; + // + ClientName(bs->enemy, name, sizeof(name)); + weap = BotWeaponNameForMeansOfDeath(g_entities[bs->enemy].client->lasthurt_mod); + // + BotAI_BotInitialChat(bs, "hit_nokill", name, weap, NULL); + bs->lastchat_time = trap_AAS_Time(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_Random +================== +*/ +int BotChat_Random(bot_state_t *bs) { + float rnd; + char name[32]; + + if (bot_nochat.integer) return qfalse; + if (BotIsObserver(bs)) return qfalse; + if (bs->lastchat_time > trap_AAS_Time() - 3) return qfalse; + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + //don't chat when doing something important :) + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_RUSHBASE) return qfalse; + // + rnd = 0; + if (random() > bs->thinktime * 0.1) return qfalse; + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + if (random() > 0.25) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + if (!BotValidChatPosition(bs)) return qfalse; + // + if (bs->lastkilledplayer == bs->client) { + strcpy(name, BotRandomOpponentName(bs)); + } + else { + EasyClientName(bs->lastkilledplayer, name, sizeof(name)); + } + // + if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_MISC, 0, 1)) { + BotAI_BotInitialChat(bs, "random_misc", + BotRandomOpponentName(bs), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL); + } + else { + BotAI_BotInitialChat(bs, "random_insult", + BotRandomOpponentName(bs), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL); + } + bs->lastchat_time = trap_AAS_Time(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChatTime +================== +*/ +float BotChatTime(bot_state_t *bs) { + int cpm; + + cpm = 4000; + return 4000; +} + +/* +================== +BotChatTest +================== +*/ +void BotChatTest(bot_state_t *bs) { + + char name[32]; + char *weap; + int num, i; + + num = trap_BotNumInitialChats(bs->cs, "game_enter"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "game_enter", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "game_exit"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "game_exit", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "level_start"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "level_start", + EasyClientName(bs->client, name, 32), // 0 + NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "level_end_victory"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "level_end_victory", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "level_end_lose"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "level_end_lose", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "level_end"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "level_end", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + EasyClientName(bs->lastkilledby, name, sizeof(name)); + num = trap_BotNumInitialChats(bs->cs, "death_drown"); + for (i = 0; i < num; i++) + { + // + BotAI_BotInitialChat(bs, "death_drown", name, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_slime"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_slime", name, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_lava"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_lava", name, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_cratered"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_cratered", name, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_suicide"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_suicide", name, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_telefrag"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_telefrag", name, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_gauntlet"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_gauntlet", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_rail"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_rail", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_bfg"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_bfg", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_insult"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_insult", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_praise"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_praise", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + // + EasyClientName(bs->lastkilledplayer, name, 32); + // + num = trap_BotNumInitialChats(bs->cs, "kill_gauntlet"); + for (i = 0; i < num; i++) + { + // + BotAI_BotInitialChat(bs, "kill_gauntlet", name, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "kill_rail"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "kill_rail", name, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "kill_telefrag"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "kill_telefrag", name, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "kill_insult"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "kill_insult", name, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "kill_praise"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "kill_praise", name, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "enemy_suicide"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "enemy_suicide", name, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + ClientName(g_entities[bs->client].client->lasthurt_client, name, sizeof(name)); + weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_client); + num = trap_BotNumInitialChats(bs->cs, "hit_talking"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "hit_talking", name, weap, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "hit_nodeath"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "hit_nodeath", name, weap, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "hit_nokill"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "hit_nokill", name, weap, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + // + if (bs->lastkilledplayer == bs->client) { + strcpy(name, BotRandomOpponentName(bs)); + } + else { + EasyClientName(bs->lastkilledplayer, name, sizeof(name)); + } + // + num = trap_BotNumInitialChats(bs->cs, "random_misc"); + for (i = 0; i < num; i++) + { + // + BotAI_BotInitialChat(bs, "random_misc", + BotRandomOpponentName(bs), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "random_insult"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "random_insult", + BotRandomOpponentName(bs), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } +} diff --git a/game/ai_chat.h b/game/ai_chat.h new file mode 100644 index 0000000..b94fb77 --- /dev/null +++ b/game/ai_chat.h @@ -0,0 +1,45 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_chat.h + * + * desc: Quake3 bot AI + * + * $Archive: /StarTrek/Code-DM/game/ai_chat.h $ + * $Author: Jmonroe $ + * $Revision: 1 $ + * $Modtime: 1/21/00 10:12p $ + * $Date: 1/25/00 6:26p $ + * + *****************************************************************************/ + +// +int BotChat_EnterGame(bot_state_t *bs); +// +int BotChat_ExitGame(bot_state_t *bs); +// +int BotChat_StartLevel(bot_state_t *bs); +// +int BotChat_EndLevel(bot_state_t *bs); +// +int BotChat_HitTalking(bot_state_t *bs); +// +int BotChat_HitNoDeath(bot_state_t *bs); +// +int BotChat_HitNoKill(bot_state_t *bs); +// +int BotChat_Death(bot_state_t *bs); +// +int BotChat_Kill(bot_state_t *bs); +// +int BotChat_EnemySuicide(bot_state_t *bs); +// +int BotChat_Random(bot_state_t *bs); +//! time the selected chat takes to type in +float BotChatTime(bot_state_t *bs); +//! returns true if the bot can chat at the current position +int BotValidChatPosition(bot_state_t *bs); +//! test the initial bot chats +void BotChatTest(bot_state_t *bs); + diff --git a/game/ai_cmd.c b/game/ai_cmd.c new file mode 100644 index 0000000..1a5a021 --- /dev/null +++ b/game/ai_cmd.c @@ -0,0 +1,1560 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_cmd.c + * + * desc: Quake3 bot AI + * + * $Archive: /StarTrek/Code-DM/game/ai_cmd.c $ + * $Author: Dkramer $ + * $Revision: 3 $ + * $Modtime: 5/09/00 4:04p $ + * $Date: 5/09/00 4:14p $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + + +#ifdef DEBUG +/* +================== +BotPrintTeamGoal +================== +*/ +void BotPrintTeamGoal(bot_state_t *bs) { + char netname[MAX_NETNAME]; + float t; + + ClientName(bs->client, netname, sizeof(netname)); + t = bs->teamgoal_time - trap_AAS_Time(); + switch(bs->ltgtype) { + case LTG_TEAMHELP: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna help a team mate for %1.0f secs\n", netname, t); + break; + } + case LTG_TEAMACCOMPANY: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna accompany a team mate for %1.0f secs\n", netname, t); + break; + } + case LTG_GETFLAG: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna get the flag for %1.0f secs\n", netname, t); + break; + } + case LTG_RUSHBASE: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna rush to the base for %1.0f secs\n", netname, t); + break; + } + case LTG_RETURNFLAG: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna try to return the flag for %1.0f secs\n", netname, t); + break; + } + case LTG_DEFENDKEYAREA: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna defend a key area for %1.0f secs\n", netname, t); + break; + } + case LTG_GETITEM: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna get an item for %1.0f secs\n", netname, t); + break; + } + case LTG_KILL: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna kill someone for %1.0f secs\n", netname, t); + break; + } + case LTG_CAMP: + case LTG_CAMPORDER: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna camp for %1.0f secs\n", netname, t); + break; + } + case LTG_PATROL: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna patrol for %1.0f secs\n", netname, t); + break; + } + default: + { + if (bs->ctfroam_time > trap_AAS_Time()) { + t = bs->ctfroam_time - trap_AAS_Time(); + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna roam for %1.0f secs\n", netname, t); + } + else { + BotAI_Print(PRT_MESSAGE, "%s: I've got a regular goal\n", netname); + } + } + } +} +#endif //DEBUG + +/* +================== +BotGetItemTeamGoal + +FIXME: add stuff like "upper rocket launcher" +"the rl near the railgun", "lower grenade launcher" etc. +================== +*/ +int BotGetItemTeamGoal(char *goalname, bot_goal_t *goal) { + int i; + + if (!strlen(goalname)) return qfalse; + i = -1; + do { + i = trap_BotGetLevelItemGoal(i, goalname, goal); + if (i > 0) { + //do NOT defend dropped items + if (goal->flags & GFL_DROPPED) + continue; + return qtrue; + } + } while(i > 0); + return qfalse; +} + +/* +================== +BotGetMessageTeamGoal +================== +*/ +int BotGetMessageTeamGoal(bot_state_t *bs, char *goalname, bot_goal_t *goal) { + bot_waypoint_t *cp; + + if (BotGetItemTeamGoal(goalname, goal)) return qtrue; + + cp = BotFindWayPoint(bs->checkpoints, goalname); + if (cp) { + memcpy(goal, &cp->goal, sizeof(bot_goal_t)); + return qtrue; + } + return qfalse; +} + +/* +================== +BotGetTime +================== +*/ +float BotGetTime(bot_match_t *match) { + bot_match_t timematch; + char timestring[MAX_MESSAGE_SIZE]; + float t; + + //if the matched string has a time + if (match->subtype & ST_TIME) { + //get the time string + trap_BotMatchVariable(match, TIME, timestring, MAX_MESSAGE_SIZE); + //match it to find out if the time is in seconds or minutes + if (trap_BotFindMatch(timestring, &timematch, MTCONTEXT_TIME)) { + if (timematch.type == MSG_FOREVER) { + t = 99999999; + } + else { + trap_BotMatchVariable(&timematch, TIME, timestring, MAX_MESSAGE_SIZE); + if (timematch.type == MSG_MINUTES) t = atof(timestring) * 60; + else if (timematch.type == MSG_SECONDS) t = atof(timestring); + else t = 0; + } + //if there's a valid time + if (t > 0) return trap_AAS_Time() + t; + } + } + return 0; +} + +/* +================== +FindClientByName +================== +*/ +int FindClientByName(char *name) { + int i; + char buf[MAX_INFO_STRING]; + static int maxclis; + + if (!maxclis) + maxclis = trap_Cvar_VariableIntegerValue("sv_maxclients"); + for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) { + ClientName(i, buf, sizeof(buf)); + if (!Q_stricmp(buf, name)) return i; + } + for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) { + ClientName(i, buf, sizeof(buf)); + if (stristr(buf, name)) return i; + } + return -1; +} + +/* +================== +FindEnemyByName +================== +*/ +int FindEnemyByName(bot_state_t *bs, char *name) { + int i; + char buf[MAX_INFO_STRING]; + static int maxclis; + + if (!maxclis) + maxclis = trap_Cvar_VariableIntegerValue("sv_maxclients"); + for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) { + if (BotSameTeam(bs, i)) continue; + ClientName(i, buf, sizeof(buf)); + if (!Q_stricmp(buf, name)) return i; + } + for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) { + if (BotSameTeam(bs, i)) continue; + ClientName(i, buf, sizeof(buf)); + if (stristr(buf, name)) return i; + } + return -1; +} + +/* +================== +NumPlayersOnSameTeam +================== +*/ +int NumPlayersOnSameTeam(bot_state_t *bs) { + int i, num; + char buf[MAX_INFO_STRING]; + static int maxclis; + + if (!maxclis) + maxclis = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + num = 0; + for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, MAX_INFO_STRING); + if (strlen(buf)) { + if (BotSameTeam(bs, i+1)) num++; + } + } + return num; +} + +/* +================== +TeamPlayIsOn +================== +*/ +int BotGetPatrolWaypoints(bot_state_t *bs, bot_match_t *match) { + char keyarea[MAX_MESSAGE_SIZE]; + int patrolflags; + bot_waypoint_t *wp, *newwp, *newpatrolpoints; + bot_match_t keyareamatch; + bot_goal_t goal; + + newpatrolpoints = NULL; + patrolflags = 0; + // + trap_BotMatchVariable(match, KEYAREA, keyarea, MAX_MESSAGE_SIZE); + // + while(1) { + if (!trap_BotFindMatch(keyarea, &keyareamatch, MTCONTEXT_PATROLKEYAREA)) { + trap_EA_SayTeam(bs->client, "what do you say?"); + BotFreeWaypoints(newpatrolpoints); + bs->patrolpoints = NULL; + return qfalse; + } + trap_BotMatchVariable(&keyareamatch, KEYAREA, keyarea, MAX_MESSAGE_SIZE); + if (!BotGetMessageTeamGoal(bs, keyarea, &goal)) { + //BotAI_BotInitialChat(bs, "cannotfind", keyarea, NULL); + //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + BotFreeWaypoints(newpatrolpoints); + bs->patrolpoints = NULL; + return qfalse; + } + //create a new waypoint + newwp = BotCreateWayPoint(keyarea, goal.origin, goal.areanum); + //add the waypoint to the patrol points + newwp->next = NULL; + for (wp = newpatrolpoints; wp && wp->next; wp = wp->next); + if (!wp) { + newpatrolpoints = newwp; + newwp->prev = NULL; + } + else { + wp->next = newwp; + newwp->prev = wp; + } + // + if (keyareamatch.subtype & ST_BACK) { + patrolflags = PATROL_LOOP; + break; + } + else if (keyareamatch.subtype & ST_REVERSE) { + patrolflags = PATROL_REVERSE; + break; + } + else if (keyareamatch.subtype & ST_MORE) { + trap_BotMatchVariable(&keyareamatch, MORE, keyarea, MAX_MESSAGE_SIZE); + } + else { + break; + } + } + // + if (!newpatrolpoints || !newpatrolpoints->next) { + trap_EA_SayTeam(bs->client, "I need more key points to patrol\n"); + BotFreeWaypoints(newpatrolpoints); + newpatrolpoints = NULL; + return qfalse; + } + // + BotFreeWaypoints(bs->patrolpoints); + bs->patrolpoints = newpatrolpoints; + // + bs->curpatrolpoint = bs->patrolpoints; + bs->patrolflags = patrolflags; + // + return qtrue; +} + +/* +================== +BotAddressedToBot +================== +*/ +int BotAddressedToBot(bot_state_t *bs, bot_match_t *match) { + char addressedto[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + char name[MAX_MESSAGE_SIZE]; + char botname[128]; + int client; + bot_match_t addresseematch; + + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + if (client < 0) return qfalse; + if (!BotSameTeam(bs, client)) return qfalse; + //if the message is addressed to someone + if (match->subtype & ST_ADDRESSED) { + trap_BotMatchVariable(match, ADDRESSEE, addressedto, sizeof(addressedto)); + //the name of this bot + ClientName(bs->client, botname, 128); + // + while(trap_BotFindMatch(addressedto, &addresseematch, MTCONTEXT_ADDRESSEE)) { + if (addresseematch.type == MSG_EVERYONE) { + return qtrue; + } + else if (addresseematch.type == MSG_MULTIPLENAMES) { + trap_BotMatchVariable(&addresseematch, TEAMMATE, name, sizeof(name)); + if (strlen(name)) { + if (stristr(botname, name)) return qtrue; + if (stristr(bs->subteam, name)) return qtrue; + } + trap_BotMatchVariable(&addresseematch, MORE, addressedto, MAX_MESSAGE_SIZE); + } + else { + trap_BotMatchVariable(&addresseematch, TEAMMATE, name, MAX_MESSAGE_SIZE); + if (strlen(name)) { + if (stristr(botname, name)) return qtrue; + if (stristr(bs->subteam, name)) return qtrue; + } + break; + } + } + //Com_sprintf(buf, sizeof(buf), "not addressed to me but %s", addressedto); + //trap_EA_Say(bs->client, buf); + return qfalse; + } + else { + //make sure not everyone reacts to this message + if (random() > (float ) 1.0 / (NumPlayersOnSameTeam(bs)-1)) return qfalse; + } + return qtrue; +} + +/* +================== +BotGPSToPosition +================== +*/ +int BotGPSToPosition(char *buf, vec3_t position) { + int i, j = 0; + int num, sign; + + for (i = 0; i < 3; i++) { + num = 0; + while(buf[j] == ' ') j++; + if (buf[j] == '-') { + j++; + sign = -1; + } + else { + sign = 1; + } + while (buf[j]) { + if (buf[j] >= '0' && buf[j] <= '9') { + num = num * 10 + buf[j] - '0'; + j++; + } + else { + j++; + break; + } + } + BotAI_Print(PRT_MESSAGE, "%d\n", sign * num); + position[i] = (float) sign * num; + } + return qtrue; +} + +/* +================== +BotMatch_HelpAccompany +================== +*/ +void BotMatch_HelpAccompany(bot_state_t *bs, bot_match_t *match) { + int client, other, areanum; + char teammate[MAX_MESSAGE_SIZE], netname[MAX_MESSAGE_SIZE]; + char itemname[MAX_MESSAGE_SIZE]; + bot_match_t teammatematch; + aas_entityinfo_t entinfo; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the team mate name + trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); + //get the client to help + if (trap_BotFindMatch(teammate, &teammatematch, MTCONTEXT_TEAMMATE) && + //if someone asks for him or herself + teammatematch.type == MSG_ME) { + //get the netname + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + other = qfalse; + } + else { + //asked for someone else + client = FindClientByName(teammate); + //if this is the bot self + if (client == bs->client) { + other = qfalse; + } + else if (!BotSameTeam(bs, client)) { + //FIXME: say "I don't help the enemy" + return; + } + else { + other = qtrue; + } + } + //if the bot doesn't know who to help (FindClientByName returned -1) + if (client < 0) { + if (other) BotAI_BotInitialChat(bs, "whois", teammate, NULL); + else BotAI_BotInitialChat(bs, "whois", netname, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + //don't help or accompany yourself + if (client == bs->client) { + return; + } + // + bs->teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum && trap_AAS_AreaReachability(areanum)) { + bs->teamgoal.entitynum = client; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + } + //if no teamgoal yet + if (bs->teamgoal.entitynum < 0) { + //if near an item + if (match->subtype & ST_NEARITEM) { + //get the match variable + trap_BotMatchVariable(match, ITEM, itemname, sizeof(itemname)); + // + if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + } + } + // + if (bs->teamgoal.entitynum < 0) { + if (other) BotAI_BotInitialChat(bs, "whereis", teammate, NULL); + else BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + //the team mate + bs->teammate = client; + //last time the team mate was assumed visible + bs->teammatevisible_time = trap_AAS_Time(); + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //get the team goal time + bs->teamgoal_time = BotGetTime(match); + //set the ltg type + if (match->type == MSG_HELP) { + bs->ltgtype = LTG_TEAMHELP; + if (!bs->teamgoal_time) bs->teamgoal_time = trap_AAS_Time() + TEAM_HELP_TIME; + } + else { + bs->ltgtype = LTG_TEAMACCOMPANY; + if (!bs->teamgoal_time) bs->teamgoal_time = trap_AAS_Time() + TEAM_ACCOMPANY_TIME; + bs->formation_dist = 3.5 * 32; //3.5 meter + bs->arrive_time = 0; + } +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_DefendKeyArea +================== +*/ +void BotMatch_DefendKeyArea(bot_state_t *bs, bot_match_t *match) { + char itemname[MAX_MESSAGE_SIZE]; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the match variable + trap_BotMatchVariable(match, KEYAREA, itemname, sizeof(itemname)); + // + if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //get the team goal time + bs->teamgoal_time = BotGetTime(match); + //set the team goal time + if (!bs->teamgoal_time) bs->teamgoal_time = trap_AAS_Time() + TEAM_DEFENDKEYAREA_TIME; + //away from defending + bs->defendaway_time = 0; +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_GetItem +================== +*/ +void BotMatch_GetItem(bot_state_t *bs, bot_match_t *match) { + char itemname[MAX_MESSAGE_SIZE]; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the match variable + trap_BotMatchVariable(match, ITEM, itemname, sizeof(itemname)); + // + if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_GETITEM; + //set the team goal time + bs->teamgoal_time = trap_AAS_Time() + TEAM_GETITEM_TIME; +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_Camp +================== +*/ +void BotMatch_Camp(bot_state_t *bs, bot_match_t *match) { + int client, areanum; + char netname[MAX_MESSAGE_SIZE]; + char itemname[MAX_MESSAGE_SIZE]; + aas_entityinfo_t entinfo; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + //asked for someone else + client = FindClientByName(netname); + //if there's no valid client with this name + if (client < 0) { + BotAI_BotInitialChat(bs, "whois", netname, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + //get the match variable + trap_BotMatchVariable(match, KEYAREA, itemname, sizeof(itemname)); + //in CTF it could be the base + if (match->subtype & ST_THERE) { + //camp at the spot the bot is currently standing + bs->teamgoal.entitynum = bs->entitynum; + bs->teamgoal.areanum = bs->areanum; + VectorCopy(bs->origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + else if (match->subtype & ST_HERE) { + //if this is the bot self + if (client == bs->client) return; + // + bs->teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum && trap_AAS_AreaReachability(areanum)) { + //NOTE: just cheat and assume the bot knows where the person is + //if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, client)) { + bs->teamgoal.entitynum = client; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + //} + } + } + //if the other is not visible + if (bs->teamgoal.entitynum < 0) { + BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + } + else if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_CAMPORDER; + //get the team goal time + bs->teamgoal_time = BotGetTime(match); + //set the team goal time + if (!bs->teamgoal_time) bs->teamgoal_time = trap_AAS_Time() + TEAM_CAMP_TIME; + //the teammate that requested the camping + bs->teammate = client; + //not arrived yet + bs->arrive_time = 0; + // +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_Patrol +================== +*/ +void BotMatch_Patrol(bot_state_t *bs, bot_match_t *match) { + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the patrol waypoints + if (!BotGetPatrolWaypoints(bs, match)) return; + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_PATROL; + //get the team goal time + bs->teamgoal_time = BotGetTime(match); + //set the team goal time if not set already + if (!bs->teamgoal_time) bs->teamgoal_time = trap_AAS_Time() + TEAM_PATROL_TIME; + // +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_GetFlag +================== +*/ +void BotMatch_GetFlag(bot_state_t *bs, bot_match_t *match) { + //if not in CTF mode + if (gametype != GT_CTF || !ctf_redflag.areanum || !ctf_blueflag.areanum) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_GETFLAG; + //set the team goal time + bs->teamgoal_time = trap_AAS_Time() + CTF_GETFLAG_TIME; +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_RushBase +================== +*/ +void BotMatch_RushBase(bot_state_t *bs, bot_match_t *match) { + //if not in CTF mode + if (gametype != GT_CTF || !ctf_redflag.areanum || !ctf_blueflag.areanum) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_RUSHBASE; + //set the team goal time + bs->teamgoal_time = trap_AAS_Time() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_TaskPreference +================== +*/ +void BotMatch_TaskPreference(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_NETNAME]; + char teammatename[MAX_MESSAGE_SIZE]; + int teammate, preference; + + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) != 0) return; + + trap_BotMatchVariable(match, NETNAME, teammatename, sizeof(teammatename)); + teammate = ClientFromName(teammatename); + if (teammate < 0) return; + + preference = BotGetTeamMateCTFPreference(bs, teammate); + switch(match->subtype) + { + case ST_DEFENDER: + { + preference &= ~CTFTP_ATTACKER; + preference |= CTFTP_DEFENDER; + break; + } + case ST_ATTACKER: + { + preference &= ~CTFTP_DEFENDER; + preference |= CTFTP_ATTACKER; + break; + } + case ST_ROAMER: + { + preference &= ~(CTFTP_ATTACKER|CTFTP_DEFENDER); + break; + } + } + BotSetTeamMateCTFPreference(bs, teammate, preference); + // + EasyClientName(teammate, teammatename, sizeof(teammatename)); + BotAI_BotInitialChat(bs, "keepinmind", teammatename, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); +} + +/* +================== +BotMatch_ReturnFlag +================== +*/ +void BotMatch_ReturnFlag(bot_state_t *bs, bot_match_t *match) { + //if not in CTF mode + if (gametype != GT_CTF) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_RETURNFLAG; + //set the team goal time + bs->teamgoal_time = trap_AAS_Time() + CTF_RETURNFLAG_TIME; + bs->rushbaseaway_time = 0; +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_JoinSubteam +================== +*/ +void BotMatch_JoinSubteam(bot_state_t *bs, bot_match_t *match) { + char teammate[MAX_MESSAGE_SIZE]; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the sub team name + trap_BotMatchVariable(match, TEAMNAME, teammate, MAX_MESSAGE_SIZE); + //set the sub team name + strncpy(bs->subteam, teammate, 32); + bs->subteam[31] = '\0'; + // + BotAI_BotInitialChat(bs, "joinedteam", teammate, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); +} + +/* +================== +BotMatch_LeaveSubteam +================== +*/ +void BotMatch_LeaveSubteam(bot_state_t *bs, bot_match_t *match) { + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + if (strlen(bs->subteam)) + { + BotAI_BotInitialChat(bs, "leftteam", bs->subteam, NULL); + } //end if + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + strcpy(bs->subteam, ""); +} + +/* +================== +BotMatch_LeaveSubteam +================== +*/ +void BotMatch_WhichTeam(bot_state_t *bs, bot_match_t *match) { + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + if (strlen(bs->subteam)) { + BotAI_BotInitialChat(bs, "inteam", bs->subteam, NULL); + } + else { + BotAI_BotInitialChat(bs, "noteam", NULL); + } + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); +} + +/* +================== +BotMatch_CheckPoint +================== +*/ +void BotMatch_CheckPoint(bot_state_t *bs, bot_match_t *match) { + int areanum; + char buf[MAX_MESSAGE_SIZE]; + vec3_t position; + bot_waypoint_t *cp; + int i; + + if (!TeamPlayIsOn()) return; + // + trap_BotMatchVariable(match, POSITION, buf, MAX_MESSAGE_SIZE); + VectorClear(position); + //BotGPSToPosition(buf, position); + i = sscanf(buf, "%f %f %f", &position[0], &position[1], &position[2]); + position[2] += 0.5; + areanum = BotPointAreaNum(position); + if (!areanum) { + if (BotAddressedToBot(bs, match)) { + BotAI_BotInitialChat(bs, "checkpoint_invalid", NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + } + return; + } + // + trap_BotMatchVariable(match, NAME, buf, MAX_MESSAGE_SIZE); + //check if there already exists a checkpoint with this name + cp = BotFindWayPoint(bs->checkpoints, buf); + if (cp) { + if (cp->next) cp->next->prev = cp->prev; + if (cp->prev) cp->prev->next = cp->next; + else bs->checkpoints = cp->next; + cp->inuse = qfalse; + } + //create a new check point + cp = BotCreateWayPoint(buf, position, areanum); + //add the check point to the bot's known chech points + cp->next = bs->checkpoints; + if (bs->checkpoints) bs->checkpoints->prev = cp; + bs->checkpoints = cp; + // + if (BotAddressedToBot(bs, match)) { + Com_sprintf(buf, sizeof(buf), "%1.0f %1.0f %1.0f", cp->goal.origin[0], + cp->goal.origin[1], + cp->goal.origin[2]); + + BotAI_BotInitialChat(bs, "checkpoint_confirm", cp->name, buf, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + } +} + +/* +================== +BotMatch_FormationSpace +================== +*/ +void BotMatch_FormationSpace(bot_state_t *bs, bot_match_t *match) { + char buf[MAX_MESSAGE_SIZE]; + float space; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NUMBER, buf, MAX_MESSAGE_SIZE); + //if it's the distance in feet + if (match->subtype & ST_FEET) space = 0.3048 * 32 * atof(buf); + //else it's in meters + else space = 32 * atof(buf); + //check if the formation intervening space is valid + if (space < 48 || space > 500) space = 100; + bs->formation_dist = space; +} + +/* +================== +BotMatch_Dismiss +================== +*/ +void BotMatch_Dismiss(bot_state_t *bs, bot_match_t *match) { + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + bs->ltgtype = 0; + bs->lead_time = 0; + // + BotAI_BotInitialChat(bs, "dismissed", NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); +} + +/* +================== +BotMatch_StartTeamLeaderShip +================== +*/ +void BotMatch_StartTeamLeaderShip(bot_state_t *bs, bot_match_t *match) { + int client; + char teammate[MAX_MESSAGE_SIZE]; + + if (!TeamPlayIsOn()) return; + //if chats for him or herself + if (match->subtype & ST_I) { + //get the team mate that will be the team leader + trap_BotMatchVariable(match, NETNAME, teammate, sizeof(teammate)); + strncpy(bs->teamleader, teammate, sizeof(bs->teamleader)); + bs->teamleader[sizeof(bs->teamleader)-1] = '\0'; + } + //chats for someone else + else { + //get the team mate that will be the team leader + trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); + client = FindClientByName(teammate); + if (client >= 0) ClientName(client, bs->teamleader, sizeof(bs->teamleader)); + } +} + +/* +================== +BotMatch_StopTeamLeaderShip +================== +*/ +void BotMatch_StopTeamLeaderShip(bot_state_t *bs, bot_match_t *match) { + int client; + char teammate[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + + if (!TeamPlayIsOn()) return; + //get the team mate that stops being the team leader + trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); + //if chats for him or herself + if (match->subtype & ST_I) { + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = FindClientByName(netname); + } + //chats for someone else + else { + client = FindClientByName(teammate); + } //end else + if (client >= 0) { + if (!Q_stricmp(bs->teamleader, ClientName(client, netname, sizeof(netname)))) { + bs->teamleader[0] = '\0'; + } + } +} + +/* +================== +BotMatch_WhoIsTeamLeader +================== +*/ +void BotMatch_WhoIsTeamLeader(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + + if (!TeamPlayIsOn()) return; + + ClientName(bs->client, netname, sizeof(netname)); + //if this bot IS the team leader + if (!Q_stricmp(netname, bs->teamleader)) { + trap_EA_SayTeam(bs->client, "I'm the team leader\n"); + } +} + +/* +================== +BotMatch_WhatAreYouDoing +================== +*/ +void BotMatch_WhatAreYouDoing(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + char goalname[MAX_MESSAGE_SIZE]; + + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + switch(bs->ltgtype) { + case LTG_TEAMHELP: + { + EasyClientName(bs->teammate, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "helping", netname, NULL); + break; + } + case LTG_TEAMACCOMPANY: + { + EasyClientName(bs->teammate, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "accompanying", netname, NULL); + break; + } + case LTG_DEFENDKEYAREA: + { + trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + BotAI_BotInitialChat(bs, "defending", goalname, NULL); + break; + } + case LTG_GETITEM: + { + trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + BotAI_BotInitialChat(bs, "gettingitem", goalname, NULL); + break; + } + case LTG_KILL: + { + ClientName(bs->teamgoal.entitynum, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "killing", netname, NULL); + break; + } + case LTG_CAMP: + case LTG_CAMPORDER: + { + BotAI_BotInitialChat(bs, "camping", NULL); + break; + } + case LTG_PATROL: + { + BotAI_BotInitialChat(bs, "patrolling", NULL); + break; + } + case LTG_GETFLAG: + { + BotAI_BotInitialChat(bs, "capturingflag", NULL); + break; + } + case LTG_RUSHBASE: + { + BotAI_BotInitialChat(bs, "rushingbase", NULL); + break; + } + case LTG_RETURNFLAG: + { + BotAI_BotInitialChat(bs, "returningflag", NULL); + break; + } + default: + { + BotAI_BotInitialChat(bs, "roaming", NULL); + break; + } + } + //chat what the bot is doing + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); +} + +/* +================== +BotMatch_WhatIsMyCommand +================== +*/ +void BotMatch_WhatIsMyCommand(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_NETNAME]; + + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) != 0) return; + bs->forceorders = qtrue; +} + +/* +================== +BotNearestVisibleItem +================== +*/ +float BotNearestVisibleItem(bot_state_t *bs, char *itemname, bot_goal_t *goal) { + int i; + char name[64]; + bot_goal_t tmpgoal; + float dist, bestdist; + vec3_t dir; + bsp_trace_t trace; + + bestdist = 999999; + i = -1; + do { + i = trap_BotGetLevelItemGoal(i, itemname, &tmpgoal); + trap_BotGoalName(tmpgoal.number, name, sizeof(name)); + if (Q_stricmp(itemname, name) != 0) continue; + VectorSubtract(tmpgoal.origin, bs->origin, dir); + dist = VectorLength(dir); + if (dist < bestdist) { + //trace from start to end + BotAI_Trace(&trace, bs->eye, NULL, NULL, tmpgoal.origin, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (trace.fraction >= 1.0) { + bestdist = dist; + memcpy(goal, &tmpgoal, sizeof(bot_goal_t)); + } + } + } while(i > 0); + return bestdist; +} + +/* +================== +BotMatch_WhereAreYou +================== +*/ +void BotMatch_WhereAreYou(bot_state_t *bs, bot_match_t *match) { + float dist, bestdist; + int i, bestitem, redflagtt, blueflagtt, redtobluett; + bot_goal_t goal; + char *nearbyitems[] = { + "Phaser Compression Rifle", + "I-MOD", + "Scavenger Weapon", + "Stasis Weapon", + "Compound Grenade Launcher", + "Tetryon Pulse Disruptor", + "Dermal Regenerator", + "Photon Burst", + "Quantum Weapon Enhancer",//fixme! + "Nano-Regenerative Protoplasmer", + "Metaphasic Shielding", + "Temporal Accelerator", + "Personal Cloaking Device", + "Anti-Gravity Pack", + "Personal Deflector Screen", + "Isokinetic Deflector Screen", + "Red Flag", + "Blue Flag", + NULL + }; + // + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + + bestitem = -1; + bestdist = 999999; + for (i = 0; nearbyitems[i]; i++) { + dist = BotNearestVisibleItem(bs, nearbyitems[i], &goal); + if (dist < bestdist) { + bestdist = dist; + bestitem = i; + } + } + if (bestitem != -1) { + if (gametype == GT_CTF) { + redflagtt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, ctf_redflag.areanum, TFL_DEFAULT); + blueflagtt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, ctf_blueflag.areanum, TFL_DEFAULT); + redtobluett = trap_AAS_AreaTravelTimeToGoalArea(ctf_redflag.areanum, ctf_redflag.origin, ctf_blueflag.areanum, TFL_DEFAULT); + if (redflagtt < (redflagtt + blueflagtt) * 0.4) { + BotAI_BotInitialChat(bs, "ctflocation", nearbyitems[bestitem], "red", NULL); + } + else if (blueflagtt < (redflagtt + blueflagtt) * 0.4) { + BotAI_BotInitialChat(bs, "ctflocation", nearbyitems[bestitem], "blue", NULL); + } + else { + BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); + } + } + else { + BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); + } + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + } +} + +/* +================== +BotMatch_LeadTheWay +================== +*/ +void BotMatch_LeadTheWay(bot_state_t *bs, bot_match_t *match) { + aas_entityinfo_t entinfo; + char netname[MAX_MESSAGE_SIZE], teammate[MAX_MESSAGE_SIZE]; + int client, areanum, other; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //if someone asks for someone else + if (match->subtype & ST_SOMEONE) { + //get the team mate name + trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); + client = FindClientByName(teammate); + //if this is the bot self + if (client == bs->client) { + other = qfalse; + } + else if (!BotSameTeam(bs, client)) { + //FIXME: say "I don't help the enemy" + return; + } + else { + other = qtrue; + } + } + else { + //get the netname + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + other = qfalse; + } + //if the bot doesn't know who to help (FindClientByName returned -1) + if (client < 0) { + BotAI_BotInitialChat(bs, "whois", netname, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + // + bs->lead_teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum && trap_AAS_AreaReachability(areanum)) { + bs->lead_teamgoal.entitynum = client; + bs->lead_teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->lead_teamgoal.origin); + VectorSet(bs->lead_teamgoal.mins, -8, -8, -8); + VectorSet(bs->lead_teamgoal.maxs, 8, 8, 8); + } + } + + if (bs->teamgoal.entitynum < 0) { + if (other) BotAI_BotInitialChat(bs, "whereis", teammate, NULL); + else BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + bs->lead_teammate = client; + bs->lead_time = trap_AAS_Time() + TEAM_LEAD_TIME; + bs->leadvisible_time = 0; + bs->leadmessage_time = -(trap_AAS_Time() + 2 * random()); +} + +/* +================== +BotMatch_Kill +================== +*/ +void BotMatch_Kill(bot_state_t *bs, bot_match_t *match) { + char enemy[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + + trap_BotMatchVariable(match, ENEMY, enemy, sizeof(enemy)); + // + client = FindEnemyByName(bs, enemy); + if (client < 0) { + BotAI_BotInitialChat(bs, "whois", enemy, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + bs->teamgoal.entitynum = client; + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_KILL; + //set the team goal time + bs->teamgoal_time = trap_AAS_Time() + TEAM_KILL_SOMEONE; +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_CTF +================== +*/ +void BotMatch_CTF(bot_state_t *bs, bot_match_t *match) { + + char flag[128], netname[MAX_NETNAME]; + + trap_BotMatchVariable(match, FLAG, flag, sizeof(flag)); + if (match->subtype & ST_GOTFLAG) { + if (!Q_stricmp(flag, "red")) { + bs->redflagstatus = 1; + if (BotCTFTeam(bs) == CTF_TEAM_BLUE) { + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + bs->flagcarrier = ClientFromName(netname); + } + } + else { + bs->blueflagstatus = 1; + if (BotCTFTeam(bs) == CTF_TEAM_RED) { + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + bs->flagcarrier = ClientFromName(netname); + } + } + bs->flagstatuschanged = 1; + bs->lastflagcapture_time = trap_AAS_Time(); + } + else if (match->subtype & ST_CAPTUREDFLAG) { + bs->redflagstatus = 0; + bs->blueflagstatus = 0; + bs->flagcarrier = 0; + bs->flagstatuschanged = 1; + } + else if (match->subtype & ST_RETURNEDFLAG) { + if (!Q_stricmp(flag, "red")) bs->redflagstatus = 0; + else bs->blueflagstatus = 0; + bs->flagstatuschanged = 1; + } +} + +/* +================== +BotMatchMessage +================== +*/ +int BotMatchMessage(bot_state_t *bs, char *message) { + bot_match_t match; + + match.type = 0; + //if it is an unknown message + if (!trap_BotFindMatch(message, &match, MTCONTEXT_ENTERGAME + |MTCONTEXT_INITIALTEAMCHAT + |MTCONTEXT_CTF)) { + return qfalse; + } + //react to the found message + switch(match.type) + { + case MSG_HELP: //someone calling for help + case MSG_ACCOMPANY: //someone calling for company + { + BotMatch_HelpAccompany(bs, &match); + break; + } + case MSG_DEFENDKEYAREA: //teamplay defend a key area + { + BotMatch_DefendKeyArea(bs, &match); + break; + } + case MSG_CAMP: //camp somewhere + { + BotMatch_Camp(bs, &match); + break; + } + case MSG_PATROL: //patrol between several key areas + { + BotMatch_Patrol(bs, &match); + break; + } +#ifdef CTF + case MSG_GETFLAG: //ctf get the enemy flag + { + BotMatch_GetFlag(bs, &match); + break; + } + case MSG_RUSHBASE: //ctf rush to the base + { + BotMatch_RushBase(bs, &match); + break; + } + case MSG_RETURNFLAG: + { + BotMatch_ReturnFlag(bs, &match); + break; + } + case MSG_CTFTASKPREFERENCE: + { + BotMatch_TaskPreference(bs, &match); + break; + } + case MSG_CTF: + { + BotMatch_CTF(bs, &match); + break; + } +#endif //CTF + case MSG_GETITEM: + { + BotMatch_GetItem(bs, &match); + break; + } + case MSG_JOINSUBTEAM: //join a sub team + { + BotMatch_JoinSubteam(bs, &match); + break; + } + case MSG_LEAVESUBTEAM: //leave a sub team + { + BotMatch_LeaveSubteam(bs, &match); + break; + } + case MSG_WHICHTEAM: + { + BotMatch_WhichTeam(bs, &match); + break; + } + case MSG_CHECKPOINT: //remember a check point + { + BotMatch_CheckPoint(bs, &match); + break; + } + case MSG_CREATENEWFORMATION: //start the creation of a new formation + { + trap_EA_SayTeam(bs->client, "the part of my brain to create formations has been damaged"); + break; + } + case MSG_FORMATIONPOSITION: //tell someone his/her position in the formation + { + trap_EA_SayTeam(bs->client, "the part of my brain to create formations has been damaged"); + break; + } + case MSG_FORMATIONSPACE: //set the formation space + { + BotMatch_FormationSpace(bs, &match); + break; + } + case MSG_DOFORMATION: //form a certain formation + { + break; + } + case MSG_DISMISS: //dismiss someone + { + BotMatch_Dismiss(bs, &match); + break; + } + case MSG_STARTTEAMLEADERSHIP: //someone will become the team leader + { + BotMatch_StartTeamLeaderShip(bs, &match); + break; + } + case MSG_STOPTEAMLEADERSHIP: //someone will stop being the team leader + { + BotMatch_StopTeamLeaderShip(bs, &match); + break; + } + case MSG_WHOISTEAMLAEDER: + { + BotMatch_WhoIsTeamLeader(bs, &match); + break; + } + case MSG_WHATAREYOUDOING: //ask a bot what he/she is doing + { + BotMatch_WhatAreYouDoing(bs, &match); + break; + } + case MSG_WHATISMYCOMMAND: + { + BotMatch_WhatIsMyCommand(bs, &match); + break; + } + case MSG_WHEREAREYOU: + { + BotMatch_WhereAreYou(bs, &match); + break; + } + case MSG_LEADTHEWAY: + { + BotMatch_LeadTheWay(bs, &match); + break; + } + case MSG_KILL: + { + BotMatch_Kill(bs, &match); + break; + } + case MSG_ENTERGAME: //someone entered the game + { + //NOTE: eliza chats will catch this + //BotMatchVariable(&match, NETNAME, netname); + //Com_sprintf(buf, sizeof(buf), "heya %s", netname); + //EA_Say(bs->client, buf); + break; + } + case MSG_WAIT: + { + break; + } + default: + { + BotAI_Print(PRT_MESSAGE, "unknown match type\n"); + break; + } + } + return qtrue; +} diff --git a/game/ai_cmd.h b/game/ai_cmd.h new file mode 100644 index 0000000..a4866be --- /dev/null +++ b/game/ai_cmd.h @@ -0,0 +1,19 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_cmd.h + * + * desc: Quake3 bot AI + * + * $Archive: /StarTrek/Code-DM/game/ai_cmd.h $ + * $Author: Jmonroe $ + * $Revision: 1 $ + * $Modtime: 1/21/00 10:12p $ + * $Date: 1/25/00 6:26p $ + * + *****************************************************************************/ + +int BotMatchMessage(bot_state_t *bs, char *message); +void BotPrintTeamGoal(bot_state_t *bs); + diff --git a/game/ai_dmnet.c b/game/ai_dmnet.c new file mode 100644 index 0000000..e9fab71 --- /dev/null +++ b/game/ai_dmnet.c @@ -0,0 +1,2030 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_dmnet.c + * + * desc: Quake3 bot AI + * + * $Archive: /StarTrek/Code-DM/game/ai_dmnet.c $ + * $Author: Mgummelt $ + * $Revision: 7 $ + * $Modtime: 3/28/01 11:12a $ + * $Date: 3/28/01 11:15a $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +//data file headers +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +//goal flag, see be_ai_goal.h for the other GFL_* +#define GFL_AIR 128 + +int numnodeswitches; +char nodeswitch[MAX_NODESWITCHES+1][144]; + +#define LOOKAHEAD_DISTANCE 300 + +/* +================== +BotResetNodeSwitches +================== +*/ +void BotResetNodeSwitches(void) { + numnodeswitches = 0; +} + +/* +================== +BotDumpNodeSwitches +================== +*/ +void BotDumpNodeSwitches(bot_state_t *bs) { + int i; + char netname[MAX_NETNAME]; + + ClientName(bs->client, netname, sizeof(netname)); + BotAI_Print(PRT_MESSAGE, "%s at %1.1f switched more than %d AI nodes\n", netname, trap_AAS_Time(), MAX_NODESWITCHES); + for (i = 0; i < numnodeswitches; i++) { + BotAI_Print(PRT_MESSAGE, nodeswitch[i]); + } + BotAI_Print(PRT_FATAL, ""); +} + +/* +================== +BotRecordNodeSwitch +================== +*/ +void BotRecordNodeSwitch(bot_state_t *bs, char *node, char *str) { + char netname[MAX_NETNAME]; + + ClientName(bs->client, netname, sizeof(netname)); + Com_sprintf(nodeswitch[numnodeswitches], 144, "%s at %2.1f entered %s: %s\n", netname, trap_AAS_Time(), node, str); +#ifdef DEBUG + if (0) { + BotAI_Print(PRT_MESSAGE, nodeswitch[numnodeswitches]); + } +#endif //DEBUG + numnodeswitches++; +} + +/* +================== +BotGetAirGoal +================== +*/ +int BotGetAirGoal(bot_state_t *bs, bot_goal_t *goal) { + bsp_trace_t bsptrace; + vec3_t end, mins = {-15, -15, -2}, maxs = {15, 15, 2}; + int areanum; + + //trace up until we hit solid + VectorCopy(bs->origin, end); + end[2] += 1000; + BotAI_Trace(&bsptrace, bs->origin, mins, maxs, end, bs->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + //trace down until we hit water + VectorCopy(bsptrace.endpos, end); + BotAI_Trace(&bsptrace, end, mins, maxs, bs->origin, bs->entitynum, CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA); + //if we found the water surface + if (bsptrace.fraction > 0) { + areanum = BotPointAreaNum(bsptrace.endpos); + if (areanum) { + VectorCopy(bsptrace.endpos, goal->origin); + goal->origin[2] -= 2; + goal->areanum = areanum; + goal->mins[0] = -15; + goal->mins[1] = -15; + goal->mins[2] = -1; + goal->maxs[0] = 15; + goal->maxs[1] = 15; + goal->maxs[2] = 1; + goal->flags = GFL_AIR; + goal->number = 0; + goal->iteminfo = 0; + goal->entitynum = 0; + return qtrue; + } + } + return qfalse; +} + +/* +================== +BotGoForAir +================== +*/ +int BotGoForAir(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) { + bot_goal_t goal; + + //if the bot needs air + if (bs->lastair_time < trap_AAS_Time() - 6) { + // +#ifdef DEBUG + //BotAI_Print(PRT_MESSAGE, "going for air\n"); +#endif //DEBUG + //if we can find an air goal + if (BotGetAirGoal(bs, &goal)) { + trap_BotPushGoal(bs->gs, &goal); + return qtrue; + } + else { + qboolean botRoamsOnly = qtrue; + /*if ( bs->cur_ps.persistant[PERS_CLASS]!=PC_NOCLASS && bs->cur_ps.persistant[PERS_CLASS]!=PC_ACTIONHERO ) + { + botRoamsOnly = qtrue; + }*/ + if ( g_pModDisintegration.integer != 0 ) + { + botRoamsOnly = qtrue; + } + //get a nearby goal outside the water + while( trap_BotChooseNBGItem( bs->gs, bs->origin, bs->inventory, tfl, ltg, range, botRoamsOnly ) ) { + trap_BotGetTopGoal(bs->gs, &goal); + //if the goal is not in water + if (!(trap_AAS_PointContents(goal.origin) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA))) { + return qtrue; + } + trap_BotPopGoal(bs->gs); + } + trap_BotResetAvoidGoals(bs->gs); + } + } + return qfalse; +} + +/* +================== +BotNearbyGoal +================== +*/ +int BotNearbyGoal(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) { + int ret; + qboolean botRoamsOnly = qtrue; + + //check if the bot should go for air + if (BotGoForAir(bs, tfl, ltg, range)) return qtrue; + //if the bot is carrying the enemy flag + if (BotCTFCarryingFlag(bs)) { + //if the bot is just a few secs away from the base + if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, + bs->teamgoal.areanum, TFL_DEFAULT) < 300) { + //make the range really small + range = 50; + } + } + // + /*if ( bs->cur_ps.persistant[PERS_CLASS]!=PC_NOCLASS && bs->cur_ps.persistant[PERS_CLASS]!=PC_ACTIONHERO ) + { + botRoamsOnly = qtrue; + }*/ + if ( g_pModDisintegration.integer != 0 ) + { + botRoamsOnly = qtrue; + } + ret = trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range, botRoamsOnly ); + /* + if (ret) + { + char buf[128]; + //get the goal at the top of the stack + trap_BotGetTopGoal(bs->gs, &goal); + trap_BotGoalName(goal.number, buf, sizeof(buf)); + BotAI_Print(PRT_MESSAGE, "%1.1f: new nearby goal %s\n", trap_AAS_Time(), buf); + } + //*/ + return ret; +} + +/* +================== +BotReachedGoal +================== +*/ +int BotReachedGoal(bot_state_t *bs, bot_goal_t *goal) { + if (goal->flags & GFL_ITEM) { + //if touching the goal + if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue; + //if the goal isn't there + if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) return qtrue; + //if in the goal area and below or above the goal and not swimming + if (bs->areanum == goal->areanum) { + if (bs->origin[0] > goal->origin[0] + goal->mins[0] && bs->origin[0] < goal->origin[0] + goal->maxs[0]) { + if (bs->origin[1] > goal->origin[1] + goal->mins[1] && bs->origin[1] < goal->origin[1] + goal->maxs[1]) { + if (!trap_AAS_Swimming(bs->origin)) { + return qtrue; + } + } + } + } + } + else if (goal->flags & GFL_AIR) { + //if touching the goal + if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue; + //if the bot got air + if (bs->lastair_time > trap_AAS_Time() - 1) return qtrue; + } + else { + //if touching the goal + if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue; + } + return qfalse; +} + +/* +================== +BotGetItemLongTermGoal +================== +*/ +int BotGetItemLongTermGoal(bot_state_t *bs, int tfl, bot_goal_t *goal) { + qboolean botRoamsOnly = qtrue; + //if the bot has no goal + if (!trap_BotGetTopGoal(bs->gs, goal)) { + //BotAI_Print(PRT_MESSAGE, "no ltg on stack\n"); + bs->ltg_time = 0; + } + //if the bot touches the current goal + else if (BotReachedGoal(bs, goal)) { + BotChooseWeapon(bs); + bs->ltg_time = 0; + } + //if it is time to find a new long term goal + if (bs->ltg_time < trap_AAS_Time()) { + //pop the current goal from the stack + trap_BotPopGoal(bs->gs); + //BotAI_Print(PRT_MESSAGE, "%s: choosing new ltg\n", ClientName(bs->client, netname, sizeof(netname))); + //choose a new goal + //BotAI_Print(PRT_MESSAGE, "%6.1f client %d: BotChooseLTGItem\n", trap_AAS_Time(), bs->client); + /*if ( bs->cur_ps.persistant[PERS_CLASS]!=PC_NOCLASS && bs->cur_ps.persistant[PERS_CLASS]!=PC_ACTIONHERO ) + { + botRoamsOnly = qtrue; + }*/ + if ( g_pModDisintegration.integer != 0 ) + { + botRoamsOnly = qtrue; + } + if (trap_BotChooseLTGItem(bs->gs, bs->origin, bs->inventory, tfl, botRoamsOnly)) { + /* + char buf[128]; + //get the goal at the top of the stack + trap_BotGetTopGoal(bs->gs, goal); + trap_BotGoalName(goal->number, buf, sizeof(buf)); + BotAI_Print(PRT_MESSAGE, "%1.1f: new long term goal %s\n", trap_AAS_Time(), buf); + //*/ + bs->ltg_time = trap_AAS_Time() + 20; + } + else {//the bot gets sorta stuck with all the avoid timings, shouldn't happen though + // +#ifdef DEBUG + //char netname[128]; + + //BotAI_Print(PRT_MESSAGE, "%s: no valid ltg (probably stuck)\n", ClientName(bs->client, netname, sizeof(netname))); +#endif + //trap_BotDumpAvoidGoals(bs->gs); + //reset the avoid goals and the avoid reach + trap_BotResetAvoidGoals(bs->gs); + trap_BotResetAvoidReach(bs->ms); + } + //get the goal at the top of the stack + return trap_BotGetTopGoal(bs->gs, goal); + } + return qtrue; +} + +/* +================== +BotGetLongTermGoal + +we could also create a seperate AI node for every long term goal type +however this saves us a lot of code +================== +*/ +int BotGetLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) { + vec3_t target, dir; + char netname[MAX_NETNAME]; + char buf[MAX_MESSAGE_SIZE]; + int areanum; + float croucher; + aas_entityinfo_t entinfo; + bot_waypoint_t *wp; + + if (bs->ltgtype == LTG_TEAMHELP && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) { + BotAI_BotInitialChat(bs, "help_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + bs->teammessage_time = 0; + } + //if trying to help the team mate for more than a minute + if (bs->teamgoal_time < trap_AAS_Time()) + bs->ltgtype = 0; + //if the team mate IS visible for quite some time + if (bs->teammatevisible_time < trap_AAS_Time() - 10) bs->ltgtype = 0; + //get entity information of the companion + BotEntityInfo(bs->teammate, &entinfo); + //if the team mate is visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) { + //if close just stand still there + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLength(dir) < 100) { + trap_BotResetAvoidReach(bs->ms); + return qfalse; + } + } + else { + //last time the bot was NOT visible + bs->teammatevisible_time = trap_AAS_Time(); + } + //if the entity information is valid (entity in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum && trap_AAS_AreaReachability(areanum)) { + //update team goal + bs->teamgoal.entitynum = bs->teammate; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + } + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + return qtrue; + } + //if the bot accompanies someone + if (bs->ltgtype == LTG_TEAMACCOMPANY && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) { + BotAI_BotInitialChat(bs, "accompany_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + bs->teammessage_time = 0; + } + //if accompanying the companion for 3 minutes + if (bs->teamgoal_time < trap_AAS_Time()) { + BotAI_BotInitialChat(bs, "accompany_stop", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + bs->ltgtype = 0; + } + //get entity information of the companion + BotEntityInfo(bs->teammate, &entinfo); + //if the companion is visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) { + //update visible time + bs->teammatevisible_time = trap_AAS_Time(); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLength(dir) < bs->formation_dist) { + //check if the bot wants to crouch + //don't crouch if crouched less than 5 seconds ago + if (bs->attackcrouch_time < trap_AAS_Time() - 5) { + croucher = 1; + if (random() < bs->thinktime * croucher) { + bs->attackcrouch_time = trap_AAS_Time() + 5 + croucher * 15; + } + } + //don't crouch when swimming + if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = trap_AAS_Time() - 1; + //if not arrived yet or arived some time ago + if (bs->arrive_time < trap_AAS_Time() - 2) { + //if not arrived yet + if (!bs->arrive_time) { + trap_EA_Gesture(bs->client); + BotAI_BotInitialChat(bs, "accompany_arrive", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + bs->arrive_time = trap_AAS_Time(); + } + //if the bot wants to crouch + else if (bs->attackcrouch_time > trap_AAS_Time()) { + trap_EA_Crouch(bs->client); + } + //else do some model taunts + else if (random() < bs->thinktime * 0.3) { + //do a gesture :) + trap_EA_Gesture(bs->client); + } + } + //if just arrived look at the companion + if (bs->arrive_time > trap_AAS_Time() - 2) { + VectorSubtract(entinfo.origin, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + //else look strategically around for enemies + else if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + //check if the bot wants to go for air + if (BotGoForAir(bs, bs->tfl, &bs->teamgoal, 400)) { + trap_BotResetLastAvoidReach(bs->ms); + //get the goal at the top of the stack + //trap_BotGetTopGoal(bs->gs, &tmpgoal); + //trap_BotGoalName(tmpgoal.number, buf, 144); + //BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf); + //time the bot gets to pick up the nearby goal item + bs->nbg_time = trap_AAS_Time() + 8; + AIEnter_Seek_NBG(bs); + return qfalse; + } + // + trap_BotResetAvoidReach(bs->ms); + return qfalse; + } + } + //if the entity information is valid (entity in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum && trap_AAS_AreaReachability(areanum)) { + //update team goal so bot will accompany + bs->teamgoal.entitynum = bs->teammate; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + } + //the goal the bot should go for + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + //if the companion is NOT visible for too long + if (bs->teammatevisible_time < trap_AAS_Time() - 60) { + BotAI_BotInitialChat(bs, "accompany_cannotfind", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + bs->ltgtype = 0; + } + return qtrue; + } + // + if (bs->ltgtype == LTG_DEFENDKEYAREA) { + if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, + bs->teamgoal.areanum, TFL_DEFAULT) > bs->defendaway_range) { + bs->defendaway_time = 0; + } + } + //if defending a key area + if (bs->ltgtype == LTG_DEFENDKEYAREA && !retreat && + bs->defendaway_time < trap_AAS_Time()) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) { + trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "defend_start", buf, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + bs->teammessage_time = 0; + } + //set the bot goal + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + //stop after 2 minutes + if (bs->teamgoal_time < trap_AAS_Time()) { + trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "defend_stop", buf, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + bs->ltgtype = 0; + } + //if very close... go away for some time + VectorSubtract(goal->origin, bs->origin, dir); + if (VectorLength(dir) < 70) { + trap_BotResetAvoidReach(bs->ms); + bs->defendaway_time = trap_AAS_Time() + 2 + 5 * random(); + bs->defendaway_range = 250; + } + return qtrue; + } + //going to kill someone + if (bs->ltgtype == LTG_KILL && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) { + EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "kill_start", buf, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + bs->teammessage_time = 0; + } + // + if (bs->lastkilledplayer == bs->teamgoal.entitynum) { + EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "kill_done", buf, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + bs->lastkilledplayer = -1; + bs->ltgtype = 0; + } + // + if (bs->teamgoal_time < trap_AAS_Time()) { + bs->ltgtype = 0; + } + //just roam around + return BotGetItemLongTermGoal(bs, tfl, goal); + } + //get an item + if (bs->ltgtype == LTG_GETITEM && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) { + trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "getitem_start", buf, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + bs->teammessage_time = 0; + } + //set the bot goal + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + //stop after some time + if (bs->teamgoal_time < trap_AAS_Time()) { + bs->ltgtype = 0; + } + // + if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) { + trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "getitem_notthere", buf, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + bs->ltgtype = 0; + } + else if (BotReachedGoal(bs, goal)) { + trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "getitem_gotit", buf, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + bs->ltgtype = 0; + } + return qtrue; + } + //if camping somewhere + if ((bs->ltgtype == LTG_CAMP || bs->ltgtype == LTG_CAMPORDER) && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) { + if (bs->ltgtype == LTG_CAMPORDER) { + BotAI_BotInitialChat(bs, "camp_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + } + bs->teammessage_time = 0; + } + //set the bot goal + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + // + if (bs->teamgoal_time < trap_AAS_Time()) { + if (bs->ltgtype == LTG_CAMPORDER) { + BotAI_BotInitialChat(bs, "camp_stop", NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + } + bs->ltgtype = 0; + } + //if really near the camp spot + VectorSubtract(goal->origin, bs->origin, dir); + if (VectorLength(dir) < 60) + { + //if not arrived yet + if (!bs->arrive_time) { + if (bs->ltgtype == LTG_CAMPORDER) { + BotAI_BotInitialChat(bs, "camp_arrive", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + } + bs->arrive_time = trap_AAS_Time(); + } + //look strategically around for enemies + if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + //check if the bot wants to crouch + //don't crouch if crouched less than 5 seconds ago + if (bs->attackcrouch_time < trap_AAS_Time() - 5) { + croucher = 1; + if (random() < bs->thinktime * croucher) { + bs->attackcrouch_time = trap_AAS_Time() + 5 + croucher * 15; + } + } + //if the bot wants to crouch + if (bs->attackcrouch_time > trap_AAS_Time()) { + trap_EA_Crouch(bs->client); + } + //don't crouch when swimming + if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = trap_AAS_Time() - 1; + //make sure the bot is not gonna drown + if (trap_PointContents(bs->eye,bs->entitynum) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) { + if (bs->ltgtype == LTG_CAMPORDER) { + BotAI_BotInitialChat(bs, "camp_stop", NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + } + bs->ltgtype = 0; + } + // + if (bs->camp_range > 0) { + //FIXME: move around a bit + } + // + trap_BotResetAvoidReach(bs->ms); + return qfalse; + } + return qtrue; + } + //patrolling along several waypoints + if (bs->ltgtype == LTG_PATROL && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) { + strcpy(buf, ""); + for (wp = bs->patrolpoints; wp; wp = wp->next) { + strcat(buf, wp->name); + if (wp->next) strcat(buf, " to "); + } + BotAI_BotInitialChat(bs, "patrol_start", buf, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + bs->teammessage_time = 0; + } + // + if (!bs->curpatrolpoint) { + bs->ltgtype = 0; + return qfalse; + } + //if the bot touches the current goal + if (trap_BotTouchingGoal(bs->origin, &bs->curpatrolpoint->goal)) { + if (bs->patrolflags & PATROL_BACK) { + if (bs->curpatrolpoint->prev) { + bs->curpatrolpoint = bs->curpatrolpoint->prev; + } + else { + bs->curpatrolpoint = bs->curpatrolpoint->next; + bs->patrolflags &= ~PATROL_BACK; + } + } + else { + if (bs->curpatrolpoint->next) { + bs->curpatrolpoint = bs->curpatrolpoint->next; + } + else { + bs->curpatrolpoint = bs->curpatrolpoint->prev; + bs->patrolflags |= PATROL_BACK; + } + } + } + //stop after 5 minutes + if (bs->teamgoal_time < trap_AAS_Time()) { + BotAI_BotInitialChat(bs, "patrol_stop", NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + bs->ltgtype = 0; + } + if (!bs->curpatrolpoint) { + bs->ltgtype = 0; + return qfalse; + } + memcpy(goal, &bs->curpatrolpoint->goal, sizeof(bot_goal_t)); + return qtrue; + } +#ifdef CTF + //if going for enemy flag + if (bs->ltgtype == LTG_GETFLAG) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) { + BotAI_BotInitialChat(bs, "captureflag_start", NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + bs->teammessage_time = 0; + } + // + switch(BotCTFTeam(bs)) { + case CTF_TEAM_RED: *goal = ctf_blueflag; break; + case CTF_TEAM_BLUE: *goal = ctf_redflag; break; + default: bs->ltgtype = 0; return qfalse; + } + //if touching the flag + if (trap_BotTouchingGoal(bs->origin, goal)) bs->ltgtype = 0; + //stop after 3 minutes + if (bs->teamgoal_time < trap_AAS_Time()) { +#ifdef DEBUG + BotAI_Print(PRT_MESSAGE, "%s: I quit getting the flag\n", ClientName(bs->client, netname, sizeof(netname))); +#endif //DEBUG + bs->ltgtype = 0; + } + return qtrue; + } + //if rushing to the base + if (bs->ltgtype == LTG_RUSHBASE && bs->rushbaseaway_time < trap_AAS_Time()) { + switch(BotCTFTeam(bs)) { + case CTF_TEAM_RED: *goal = ctf_redflag; break; + case CTF_TEAM_BLUE: *goal = ctf_blueflag; break; + default: bs->ltgtype = 0; return qfalse; + } + //if not carrying the flag anymore + if (!BotCTFCarryingFlag(bs)) bs->ltgtype = 0; + //quit rushing after 2 minutes + if (bs->teamgoal_time < trap_AAS_Time()) bs->ltgtype = 0; + //if touching the base flag the bot should loose the enemy flag + if (trap_BotTouchingGoal(bs->origin, goal)) { + //if the bot is still carrying the enemy flag then the + //base flag is gone, now just walk near the base a bit + if (BotCTFCarryingFlag(bs)) { + trap_BotResetAvoidReach(bs->ms); + bs->rushbaseaway_time = trap_AAS_Time() + 5 + 10 * random(); + //FIXME: add chat to tell the others to get back the flag + //FIXME: Make them camp? Get health? Preserve themselves? + } + else { + bs->ltgtype = 0; + } + } + return qtrue; + } + //returning flag + if (bs->ltgtype == LTG_RETURNFLAG) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < trap_AAS_Time()) { + EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "returnflag_start", buf, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + bs->teammessage_time = 0; + } + // + if (bs->teamgoal_time < trap_AAS_Time()) { + bs->ltgtype = 0; + } + //FIXME: Uh.... we're trying to retrieve our flag, shouldn't + // we set that as our goal somewhere? + //ALSO: Can't we also easily implement the ability to pick up + // the enemy flag if it's dropped in the field by a teammate? + + //just roam around + return BotGetItemLongTermGoal(bs, tfl, goal); + } +#endif //CTF + //normal goal stuff + return BotGetItemLongTermGoal(bs, tfl, goal); +} + +/* +================== +BotLongTermGoal +================== +*/ +int BotLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) { + aas_entityinfo_t entinfo; + char teammate[MAX_MESSAGE_SIZE]; + float dist; + int areanum; + vec3_t dir; + + //FIXME: also have air long term goals? + // + //if the bot is leading someone and not retreating + if (bs->lead_time > 0 && !retreat) { + if (bs->lead_time < trap_AAS_Time()) { + //FIXME: add chat to tell the team mate that he/she's on his/her own + bs->lead_time = 0; + return BotGetLongTermGoal(bs, tfl, retreat, goal); + } + // + if (bs->leadmessage_time < 0 && -bs->leadmessage_time < trap_AAS_Time()) { + BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + bs->leadmessage_time = trap_AAS_Time(); + } + //get entity information of the companion + BotEntityInfo(bs->lead_teammate, &entinfo); + // + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum && trap_AAS_AreaReachability(areanum)) { + //update team goal + bs->lead_teamgoal.entitynum = bs->lead_teammate; + bs->lead_teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->lead_teamgoal.origin); + VectorSet(bs->lead_teamgoal.mins, -8, -8, -8); + VectorSet(bs->lead_teamgoal.maxs, 8, 8, 8); + } + } + //if the team mate is visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->lead_teammate)) { + bs->leadvisible_time = trap_AAS_Time(); + } + //if the team mate is not visible for 1 seconds + if (bs->leadvisible_time < trap_AAS_Time() - 1) { + bs->leadbackup_time = trap_AAS_Time() + 2; + } + //distance towards the team mate + VectorSubtract(bs->origin, bs->lead_teamgoal.origin, dir); + dist = VectorLength(dir); + //if backing up towards the team mate + if (bs->leadbackup_time > trap_AAS_Time()) { + if (bs->leadmessage_time < trap_AAS_Time() - 20) { + BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + bs->leadmessage_time = trap_AAS_Time(); + } + //if very close to the team mate + if (dist < 100) { + bs->leadbackup_time = 0; + } + //the bot should go back to the team mate + memcpy(goal, &bs->lead_teamgoal, sizeof(bot_goal_t)); + return qtrue; + } + else { + //if quite distant from the team mate + if (dist > 500) { + if (bs->leadmessage_time < trap_AAS_Time() - 20) { + BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + bs->leadmessage_time = trap_AAS_Time(); + } + //look at the team mate + VectorSubtract(entinfo.origin, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + //just wait for the team mate + return qfalse; + } + } + } + return BotGetLongTermGoal(bs, tfl, retreat, goal); +} + +/* +================== +AIEnter_Intermission +================== +*/ +void AIEnter_Intermission(bot_state_t *bs) { + BotRecordNodeSwitch(bs, "intermission", ""); + //reset the bot state + BotResetState(bs); + //check for end level chat + if (BotChat_EndLevel(bs)) { + trap_BotEnterChat(bs->cs, bs->client, bs->chatto); + } + bs->ainode = AINode_Intermission; +} + +/* +================== +AINode_Intermission +================== +*/ +int AINode_Intermission(bot_state_t *bs) { + //if the intermission ended + if (!BotIntermission(bs)) { + if (BotChat_StartLevel(bs)) { + bs->stand_time = trap_AAS_Time() + BotChatTime(bs); + } + else { + bs->stand_time = trap_AAS_Time() + 2; + } + AIEnter_Stand(bs); + } + return qtrue; +} + +/* +================== +AIEnter_Observer +================== +*/ +void AIEnter_Observer(bot_state_t *bs) { + BotRecordNodeSwitch(bs, "observer", ""); + //reset the bot state + BotResetState(bs); + bs->ainode = AINode_Observer; +} + +/* +================== +AINode_Observer +================== +*/ +int AINode_Observer(bot_state_t *bs) { + //if the bot left observer mode + if (!BotIsObserver(bs)) { + AIEnter_Stand(bs); + } + return qtrue; +} + +/* +================== +AIEnter_Stand +================== +*/ +void AIEnter_Stand(bot_state_t *bs) { + BotRecordNodeSwitch(bs, "stand", ""); + bs->standfindenemy_time = trap_AAS_Time() + 1; + bs->ainode = AINode_Stand; +} + +/* +================== +AINode_Stand +================== +*/ +int AINode_Stand(bot_state_t *bs) { + + //if the bot's health decreased + if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) { + if (BotChat_HitTalking(bs)) { + bs->standfindenemy_time = trap_AAS_Time() + BotChatTime(bs) + 0.1; + bs->stand_time = trap_AAS_Time() + BotChatTime(bs) + 0.1; + } + } + if (bs->standfindenemy_time < trap_AAS_Time()) { + if (BotFindEnemy(bs, -1)) { + AIEnter_Battle_Fight(bs); + return qfalse; + } + bs->standfindenemy_time = trap_AAS_Time() + 1; + } + trap_EA_Talk(bs->client); + if (bs->stand_time < trap_AAS_Time()) { + trap_BotEnterChat(bs->cs, bs->client, bs->chatto); + AIEnter_Seek_LTG(bs); + return qfalse; + } + // + return qtrue; +} + +/* +================== +AIEnter_Respawn +================== +*/ +void AIEnter_Respawn(bot_state_t *bs) { + BotRecordNodeSwitch(bs, "respawn", ""); + //reset some states + trap_BotResetMoveState(bs->ms); + trap_BotResetGoalState(bs->gs); + trap_BotResetAvoidGoals(bs->gs); + trap_BotResetAvoidReach(bs->ms); + //if the bot wants to chat + if (BotChat_Death(bs)) { + bs->respawn_time = trap_AAS_Time() + BotChatTime(bs); + bs->respawnchat_time = trap_AAS_Time(); + } + else { + bs->respawn_time = trap_AAS_Time() + 1 + random(); + bs->respawnchat_time = 0; + } + //set respawn state + bs->respawn_wait = qfalse; + bs->ainode = AINode_Respawn; +} + +/* +================== +AINode_Respawn +================== +*/ +int AINode_Respawn(bot_state_t *bs) { + if (bs->respawn_wait) { + if (!BotIsDead(bs)) { + AIEnter_Seek_LTG(bs); + } + else { + trap_EA_Respawn(bs->client); + } + } + else if (bs->respawn_time < trap_AAS_Time()) { + //wait until respawned + bs->respawn_wait = qtrue; + //elementary action respawn + trap_EA_Respawn(bs->client); + // + if (bs->respawnchat_time) { + trap_BotEnterChat(bs->cs, bs->client, bs->chatto); + bs->enemy = -1; + } + } + if (bs->respawnchat_time && bs->respawnchat_time < trap_AAS_Time() - 0.5) { + trap_EA_Talk(bs->client); + } + // + return qtrue; +} + +/* +================== +AIEnter_Seek_ActivateEntity +================== +*/ +void AIEnter_Seek_ActivateEntity(bot_state_t *bs) { + BotRecordNodeSwitch(bs, "activate entity", ""); + bs->ainode = AINode_Seek_ActivateEntity; +} + +/* +================== +AINode_Seek_Activate_Entity +================== +*/ +int AINode_Seek_ActivateEntity(bot_state_t *bs) { + bot_goal_t *goal; + vec3_t target, dir; + bot_moveresult_t moveresult; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + //map specific code + BotMapScripts(bs); + //no enemy + bs->enemy = -1; + // + goal = &bs->activategoal; + //if the bot has no goal + if (!goal) bs->activate_time = 0; + //if the bot touches the current goal + else if (trap_BotTouchingGoal(bs->origin, goal)) { + BotChooseWeapon(bs); +#ifdef DEBUG + BotAI_Print(PRT_MESSAGE, "touched button or trigger\n"); +#endif //DEBUG + bs->activate_time = 0; + } + // + if (bs->activate_time < trap_AAS_Time()) { + AIEnter_Seek_NBG(bs); + return qfalse; + } + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + bs->nbg_time = 0; + } + //check if the bot is blocked + BotAIBlocked(bs, &moveresult, qtrue); + // + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + //if waiting for something + else if (moveresult.flags & MOVERESULT_WAITING) { + if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + } + else if (!(bs->flags & BFL_IDEALVIEWSET)) { + if (trap_BotMovementViewTarget(bs->ms, goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + else { + //vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + bs->ideal_viewangles[2] *= 0.5; + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + //if there is an enemy + if (BotFindEnemy(bs, -1)) { + if (BotWantsToRetreat(bs)) { + //keep the current long term goal and retreat + AIEnter_Battle_NBG(bs); + } + else { + trap_BotResetLastAvoidReach(bs->ms); + //empty the goal stack + trap_BotEmptyGoalStack(bs->gs); + //go fight + AIEnter_Battle_Fight(bs); + } + } + return qtrue; +} + +/* +================== +AIEnter_Seek_NBG +================== +*/ +void AIEnter_Seek_NBG(bot_state_t *bs) { + bot_goal_t goal; + char buf[144]; + + if (trap_BotGetTopGoal(bs->gs, &goal)) { + trap_BotGoalName(goal.number, buf, 144); + BotRecordNodeSwitch(bs, "seek NBG", buf); + } + else { + BotRecordNodeSwitch(bs, "seek NBG", "no goal"); + } + bs->ainode = AINode_Seek_NBG; +} + +/* +================== +AINode_Seek_NBG +================== +*/ +int AINode_Seek_NBG(bot_state_t *bs) { + bot_goal_t goal; + vec3_t target, dir; + bot_moveresult_t moveresult; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts(bs); + //no enemy + bs->enemy = -1; + //if the bot has no goal + if (!trap_BotGetTopGoal(bs->gs, &goal)) bs->nbg_time = 0; + //if the bot touches the current goal + else if (BotReachedGoal(bs, &goal)) { + BotChooseWeapon(bs); + bs->nbg_time = 0; + } + // + if (bs->nbg_time < trap_AAS_Time()) { + //pop the current goal from the stack + trap_BotPopGoal(bs->gs); + //check for new nearby items right away + //NOTE: we canNOT reset the check_time to zero because it would create an endless loop of node switches + bs->check_time = trap_AAS_Time() + 0.05; + //go back to seek ltg + AIEnter_Seek_LTG(bs); + return qfalse; + } + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + bs->nbg_time = 0; + } + //check if the bot is blocked + BotAIBlocked(bs, &moveresult, qtrue); + //if the viewangles are used for the movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + //if waiting for something + else if (moveresult.flags & MOVERESULT_WAITING) { + if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + } + else if (!(bs->flags & BFL_IDEALVIEWSET)) { + if (!trap_BotGetSecondGoal(bs->gs, &goal)) trap_BotGetTopGoal(bs->gs, &goal); + if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + //FIXME: look at cluster portals? + else vectoangles(moveresult.movedir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + //if there is an enemy + if (BotFindEnemy(bs, -1)) { + if (BotWantsToRetreat(bs)) { + //keep the current long term goal and retreat + AIEnter_Battle_NBG(bs); + } + else { + trap_BotResetLastAvoidReach(bs->ms); + //empty the goal stack + trap_BotEmptyGoalStack(bs->gs); + //go fight + AIEnter_Battle_Fight(bs); + } + } + return qtrue; +} + +/* +================== +AIEnter_Seek_LTG +================== +*/ +void AIEnter_Seek_LTG(bot_state_t *bs) { + bot_goal_t goal; + char buf[144]; + + if (trap_BotGetTopGoal(bs->gs, &goal)) { + trap_BotGoalName(goal.number, buf, 144); + BotRecordNodeSwitch(bs, "seek LTG", buf); + } + else { + BotRecordNodeSwitch(bs, "seek LTG", "no goal"); + } + bs->ainode = AINode_Seek_LTG; +} + +/* +================== +AINode_Seek_LTG +================== +*/ +int AINode_Seek_LTG(bot_state_t *bs) +{ + bot_goal_t goal; + vec3_t target, dir; + bot_moveresult_t moveresult; + int range; + //char buf[128]; + //bot_goal_t tmpgoal; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs); + return qfalse; + } + // + if (BotChat_Random(bs)) { + bs->stand_time = trap_AAS_Time() + BotChatTime(bs); + AIEnter_Stand(bs); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts(bs); + //no enemy + bs->enemy = -1; + // + if (bs->killedenemy_time > trap_AAS_Time() - 2) { + if (random() < bs->thinktime * 1) { + trap_EA_Gesture(bs->client); + } + } + //if there is an enemy + if (BotFindEnemy(bs, -1)) { + if (BotWantsToRetreat(bs)) { + //keep the current long term goal and retreat + AIEnter_Battle_Retreat(bs); + return qfalse; + } + else { + trap_BotResetLastAvoidReach(bs->ms); + //empty the goal stack + trap_BotEmptyGoalStack(bs->gs); + //go fight + AIEnter_Battle_Fight(bs); + return qfalse; + } + } +#ifdef CTF + if (gametype == GT_CTF) { + //decide what to do in CTF mode + BotCTFSeekGoals(bs); + } +#endif //CTF + //get the current long term goal + if (!BotLongTermGoal(bs, bs->tfl, qfalse, &goal)) { + return qtrue; + } + //check for nearby goals periodicly + if (bs->check_time < trap_AAS_Time()) { + bs->check_time = trap_AAS_Time() + 0.5; + //check if the bot wants to camp + BotWantsToCamp(bs); + // + if (bs->ltgtype == LTG_DEFENDKEYAREA) range = 400; + else range = 150; + // +#ifdef CTF + //if carrying a flag the bot shouldn't be distracted too much + if (BotCTFCarryingFlag(bs)) range = 50; +#endif //CTF + // + if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { + trap_BotResetLastAvoidReach(bs->ms); + //get the goal at the top of the stack + //trap_BotGetTopGoal(bs->gs, &tmpgoal); + //trap_BotGoalName(tmpgoal.number, buf, 144); + //BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf); + //time the bot gets to pick up the nearby goal item + bs->nbg_time = trap_AAS_Time() + 4 + range * 0.01; + AIEnter_Seek_NBG(bs); + return qfalse; + } + } + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qtrue); + //if the viewangles are used for the movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + //if waiting for something + else if (moveresult.flags & MOVERESULT_WAITING) { + if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + } + else if (!(bs->flags & BFL_IDEALVIEWSET)) { + if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + //FIXME: look at cluster portals? + else if (VectorLength(moveresult.movedir)) { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + else if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + bs->ideal_viewangles[2] *= 0.5; + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + // + return qtrue; +} + +/* +================== +AIEnter_Battle_Fight +================== +*/ +void AIEnter_Battle_Fight(bot_state_t *bs) { + BotRecordNodeSwitch(bs, "battle fight", ""); + trap_BotResetLastAvoidReach(bs->ms); + bs->ainode = AINode_Battle_Fight; +} + +/* +================== +AIEnter_Battle_Fight +================== +*/ +void AIEnter_Battle_SuicidalFight(bot_state_t *bs) { + BotRecordNodeSwitch(bs, "battle fight", ""); + trap_BotResetLastAvoidReach(bs->ms); + bs->ainode = AINode_Battle_Fight; + bs->flags |= BFL_FIGHTSUICIDAL; +} + +/* +================== +AINode_Battle_Fight +================== +*/ +int AINode_Battle_Fight(bot_state_t *bs) { + int areanum; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs); + return qfalse; + } + + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs); + return qfalse; + } + //if there is another better enemy + if (BotFindEnemy(bs, bs->enemy)) { +#ifdef DEBUG +// BotAI_Print(PRT_MESSAGE, "found new better enemy\n"); +#endif + } + //if no enemy + if (bs->enemy < 0) { + AIEnter_Seek_LTG(bs); + return qfalse; + } + // + BotEntityInfo(bs->enemy, &entinfo); + //if the enemy is dead + if (bs->enemydeath_time) { + if (bs->enemydeath_time < trap_AAS_Time() - 1.0) { + bs->enemydeath_time = 0; + if (bs->enemysuicide) { + BotChat_EnemySuicide(bs); + } + if (bs->lastkilledplayer == bs->enemy && BotChat_Kill(bs)) { + bs->stand_time = trap_AAS_Time() + BotChatTime(bs); + AIEnter_Stand(bs); + } + else { + bs->ltg_time = 0; + AIEnter_Seek_LTG(bs); + } + return qfalse; + } + } + else { + if (EntityIsDead(&entinfo)) { + bs->enemydeath_time = trap_AAS_Time(); + } + } + //if the enemy is invisible and not shooting the bot looses track easily + if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { + if (random() < 0.2) { + AIEnter_Seek_LTG(bs); + return qfalse; + } + } + //update the reachability area and origin if possible + areanum = BotPointAreaNum(entinfo.origin); + if (areanum && trap_AAS_AreaReachability(areanum)) { + VectorCopy(entinfo.origin, bs->lastenemyorigin); + bs->lastenemyareanum = areanum; + } + //update the attack inventory values + BotUpdateBattleInventory(bs, bs->enemy); + //if the bot's health decreased + if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) { + if (BotChat_HitNoDeath(bs)) { + bs->stand_time = trap_AAS_Time() + BotChatTime(bs); + AIEnter_Stand(bs); + return qfalse; + } + } + //if the bot hit someone + if (bs->cur_ps.persistant[PERS_HITS] > bs->lasthitcount) + { + if (BotChat_HitNoKill(bs)) + { + bs->stand_time = trap_AAS_Time() + BotChatTime(bs); + AIEnter_Stand(bs); + return qfalse; + } + } + //if the enemy is not visible + if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { + if (BotWantsToChase(bs)) { + AIEnter_Battle_Chase(bs); + return qfalse; + } + else { + AIEnter_Seek_LTG(bs); + return qfalse; + } + } + //use holdable items + BotBattleUseItems(bs); + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //choose the best weapon to fight with + BotChooseWeapon(bs); + //do attack movements + moveresult = BotAttackMove(bs, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qfalse); + //aim at the enemy + BotAimAtEnemy(bs); + //attack the enemy if possible + BotCheckAttack(bs); + //if the bot wants to retreat + if (!(bs->flags & BFL_FIGHTSUICIDAL)) { + if (BotWantsToRetreat(bs)) { + AIEnter_Battle_Retreat(bs); + return qtrue; + } + } + return qtrue; +} + +/* +================== +AIEnter_Battle_Chase +================== +*/ +void AIEnter_Battle_Chase(bot_state_t *bs) { + BotRecordNodeSwitch(bs, "battle chase", ""); + bs->chase_time = trap_AAS_Time(); + bs->ainode = AINode_Battle_Chase; +} + +/* +================== +AINode_Battle_Chase +================== +*/ +int AINode_Battle_Chase(bot_state_t *bs) +{ + bot_goal_t goal; + vec3_t target, dir; + bot_moveresult_t moveresult; + float range; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs); + return qfalse; + } + //if no enemy + if (bs->enemy < 0) { + AIEnter_Seek_LTG(bs); + return qfalse; + } + //if the enemy is visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { + AIEnter_Battle_Fight(bs); + return qfalse; + } + //if there is another enemy + if (BotFindEnemy(bs, -1)) { + AIEnter_Battle_Fight(bs); + return qfalse; + } + //there is no last enemy area + if (!bs->lastenemyareanum) { + AIEnter_Seek_LTG(bs); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts(bs); + //create the chase goal + goal.entitynum = bs->enemy; + goal.areanum = bs->lastenemyareanum; + VectorCopy(bs->lastenemyorigin, goal.origin); + VectorSet(goal.mins, -8, -8, -8); + VectorSet(goal.maxs, 8, 8, 8); + //if the last seen enemy spot is reached the enemy could not be found + if (trap_BotTouchingGoal(bs->origin, &goal)) bs->chase_time = 0; + //if there's no chase time left + if (!bs->chase_time || bs->chase_time < trap_AAS_Time() - 10) { + AIEnter_Seek_LTG(bs); + return qfalse; + } + //check for nearby goals periodicly + if (bs->check_time < trap_AAS_Time()) { + bs->check_time = trap_AAS_Time() + 1; + range = 150; + // + if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { + //the bot gets 5 seconds to pick up the nearby goal item + bs->nbg_time = trap_AAS_Time() + 0.1 * range + 1; + trap_BotResetLastAvoidReach(bs->ms); + AIEnter_Battle_NBG(bs); + return qfalse; + } + } + // + BotUpdateBattleInventory(bs, bs->enemy); + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qfalse); + // + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + else if (!(bs->flags & BFL_IDEALVIEWSET)) { + if (bs->chase_time > trap_AAS_Time() - 2) { + BotAimAtEnemy(bs); + } + else { + if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + else { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + } + bs->ideal_viewangles[2] *= 0.5; + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + //if the bot is in the area the enemy was last seen in + if (bs->areanum == bs->lastenemyareanum) bs->chase_time = 0; + //if the bot wants to retreat (the bot could have been damage during the chase) + if (BotWantsToRetreat(bs)) { + AIEnter_Battle_Retreat(bs); + return qtrue; + } + return qtrue; +} + +/* +================== +AIEnter_Battle_Retreat +================== +*/ +void AIEnter_Battle_Retreat(bot_state_t *bs) { + BotRecordNodeSwitch(bs, "battle retreat", ""); + bs->ainode = AINode_Battle_Retreat; +} + +/* +================== +AINode_Battle_Retreat +================== +*/ +int AINode_Battle_Retreat(bot_state_t *bs) { + bot_goal_t goal; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + vec3_t target, dir; + float attack_skill, range; + int areanum; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs); + return qfalse; + } + //if no enemy + if (bs->enemy < 0) { + AIEnter_Seek_LTG(bs); + return qfalse; + } + // + BotEntityInfo(bs->enemy, &entinfo); + if (EntityIsDead(&entinfo)) { + AIEnter_Seek_LTG(bs); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + //map specific code + BotMapScripts(bs); + //update the attack inventory values + BotUpdateBattleInventory(bs, bs->enemy); + //if the bot doesn't want to retreat anymore... probably picked up some nice items + if (BotWantsToChase(bs)) { + //empty the goal stack, when chasing, only the enemy is the goal + trap_BotEmptyGoalStack(bs->gs); + //go chase the enemy + AIEnter_Battle_Chase(bs); + return qfalse; + } + //update the last time the enemy was visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { + bs->enemyvisible_time = trap_AAS_Time(); + //update the reachability area and origin if possible + areanum = BotPointAreaNum(entinfo.origin); + if (areanum && trap_AAS_AreaReachability(areanum)) { + VectorCopy(entinfo.origin, bs->lastenemyorigin); + bs->lastenemyareanum = areanum; + } + } + //if the enemy is NOT visible for 4 seconds + if (bs->enemyvisible_time < trap_AAS_Time() - 4) { + AIEnter_Seek_LTG(bs); + return qfalse; + } + //else if the enemy is NOT visible + else if (bs->enemyvisible_time < trap_AAS_Time()) { + //if there is another enemy + if (BotFindEnemy(bs, -1)) { + AIEnter_Battle_Fight(bs); + return qfalse; + } + } + // +#ifdef CTF + if (gametype == GT_CTF) { + BotCTFRetreatGoals(bs); + } +#endif //CTF + //use holdable items + BotBattleUseItems(bs); + //get the current long term goal while retreating + if (!BotLongTermGoal(bs, bs->tfl, qtrue, &goal)) { + AIEnter_Battle_SuicidalFight(bs); + return qfalse; + } + //check for nearby goals periodicly + if (bs->check_time < trap_AAS_Time()) { + bs->check_time = trap_AAS_Time() + 1; + range = 150; +#ifdef CTF + //if carrying a flag the bot shouldn't be distracted too much + if (BotCTFCarryingFlag(bs)) range = 100; +#endif //CTF + // + if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { + trap_BotResetLastAvoidReach(bs->ms); + //time the bot gets to pick up the nearby goal item + bs->nbg_time = trap_AAS_Time() + range / 100 + 1; + AIEnter_Battle_NBG(bs); + return qfalse; + } + } + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qfalse); + //choose the best weapon to fight with + BotChooseWeapon(bs); + //if the view is fixed for the movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET) && !(bs->flags & BFL_IDEALVIEWSET) ) + { + attack_skill = 1; + //if the bot is skilled anough + if (attack_skill > 0.3) { + BotAimAtEnemy(bs); + } + else { + if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + else { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + bs->ideal_viewangles[2] *= 0.5; + } + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + //attack the enemy if possible + BotCheckAttack(bs); + // + return qtrue; +} + +/* +================== +AIEnter_Battle_NBG +================== +*/ +void AIEnter_Battle_NBG(bot_state_t *bs) { + BotRecordNodeSwitch(bs, "battle NBG", ""); + bs->ainode = AINode_Battle_NBG; +} + +/* +================== +AINode_Battle_NBG +================== +*/ +int AINode_Battle_NBG(bot_state_t *bs) { + int areanum; + bot_goal_t goal; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + float attack_skill; + vec3_t target, dir; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs); + return qfalse; + } + //if no enemy + if (bs->enemy < 0) { + AIEnter_Seek_NBG(bs); + return qfalse; + } + // + BotEntityInfo(bs->enemy, &entinfo); + if (EntityIsDead(&entinfo)) { + AIEnter_Seek_NBG(bs); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts(bs); + //update the last time the enemy was visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { + bs->enemyvisible_time = trap_AAS_Time(); + //update the reachability area and origin if possible + areanum = BotPointAreaNum(entinfo.origin); + if (areanum && trap_AAS_AreaReachability(areanum)) { + VectorCopy(entinfo.origin, bs->lastenemyorigin); + bs->lastenemyareanum = areanum; + } + } + //if the bot has no goal or touches the current goal + if (!trap_BotGetTopGoal(bs->gs, &goal)) { + bs->nbg_time = 0; + } + else if (trap_BotTouchingGoal(bs->origin, &goal)) { + bs->nbg_time = 0; + } + // + if (bs->nbg_time < trap_AAS_Time()) { + //pop the current goal from the stack + trap_BotPopGoal(bs->gs); + //if the bot still has a goal + if (trap_BotGetTopGoal(bs->gs, &goal)) AIEnter_Battle_Retreat(bs); + else AIEnter_Battle_Fight(bs); + // + return qfalse; + } + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->nbg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qfalse); + //update the attack inventory values + BotUpdateBattleInventory(bs, bs->enemy); + //choose the best weapon to fight with + BotChooseWeapon(bs); + //if the view is fixed for the movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET) && !(bs->flags & BFL_IDEALVIEWSET)) + { + attack_skill = 1; + //if the bot is skilled anough and the enemy is visible + if (attack_skill > 0.3) { + //&& BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy) + BotAimAtEnemy(bs); + } + else { + if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + else { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + bs->ideal_viewangles[2] *= 0.5; + } + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + //attack the enemy if possible + BotCheckAttack(bs); + // + return qtrue; +} + diff --git a/game/ai_dmnet.h b/game/ai_dmnet.h new file mode 100644 index 0000000..7cabf3a --- /dev/null +++ b/game/ai_dmnet.h @@ -0,0 +1,45 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_dmnet.h + * + * desc: Quake3 bot AI + * + * $Archive: /StarTrek/Code-DM/game/ai_dmnet.h $ + * $Author: Jmonroe $ + * $Revision: 1 $ + * $Modtime: 1/21/00 10:12p $ + * $Date: 1/25/00 6:26p $ + * + *****************************************************************************/ + +#define MAX_NODESWITCHES 50 + +void AIEnter_Intermission(bot_state_t *bs); +void AIEnter_Observer(bot_state_t *bs); +void AIEnter_Respawn(bot_state_t *bs); +void AIEnter_Stand(bot_state_t *bs); +void AIEnter_Seek_ActivateEntity(bot_state_t *bs); +void AIEnter_Seek_NBG(bot_state_t *bs); +void AIEnter_Seek_LTG(bot_state_t *bs); +void AIEnter_Seek_Camp(bot_state_t *bs); +void AIEnter_Battle_Fight(bot_state_t *bs); +void AIEnter_Battle_Chase(bot_state_t *bs); +void AIEnter_Battle_Retreat(bot_state_t *bs); +void AIEnter_Battle_NBG(bot_state_t *bs); +int AINode_Intermission(bot_state_t *bs); +int AINode_Observer(bot_state_t *bs); +int AINode_Respawn(bot_state_t *bs); +int AINode_Stand(bot_state_t *bs); +int AINode_Seek_ActivateEntity(bot_state_t *bs); +int AINode_Seek_NBG(bot_state_t *bs); +int AINode_Seek_LTG(bot_state_t *bs); +int AINode_Battle_Fight(bot_state_t *bs); +int AINode_Battle_Chase(bot_state_t *bs); +int AINode_Battle_Retreat(bot_state_t *bs); +int AINode_Battle_NBG(bot_state_t *bs); + +void BotResetNodeSwitches(void); +void BotDumpNodeSwitches(bot_state_t *bs); + diff --git a/game/ai_dmq3.c b/game/ai_dmq3.c new file mode 100644 index 0000000..a87fd00 --- /dev/null +++ b/game/ai_dmq3.c @@ -0,0 +1,2858 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +/***************************************************************************** + * name: ai_dmq3.c + * + * desc: Quake3 bot AI + * + * $Archive: /StarTrek/Code-DM/game/ai_dmq3.c $ + * $Author: Mgummelt $ + * $Revision: 33 $ + * $Modtime: 4/04/01 5:01p $ + * $Date: 4/04/01 5:17p $ + * + *****************************************************************************/ + + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +#define IDEAL_ATTACKDIST 140 +#define WEAPONINDEX_PHASER 2 + +#define MAX_WAYPOINTS 128 +// +bot_waypoint_t botai_waypoints[MAX_WAYPOINTS]; +bot_waypoint_t *botai_freewaypoints; + +//NOTE: not using a cvars which can be updated because the game should be reloaded anyway +int gametype; //game type +int maxclients; //maximum number of clients + +vmCvar_t bot_grapple; +vmCvar_t bot_rocketjump; +vmCvar_t bot_fastchat; +vmCvar_t bot_nochat; +vmCvar_t bot_testrchat; +vmCvar_t bot_challenge; + +vec3_t lastteleport_origin; //last teleport event origin +float lastteleport_time; //last teleport event time +int max_bspmodelindex; //maximum BSP model index + +//CTF flag goals +bot_goal_t ctf_redflag; +bot_goal_t ctf_blueflag; + +#ifdef CTF +/* +================== +BotCTFCarryingFlag +================== +*/ +int BotCTFCarryingFlag(bot_state_t *bs) { + if (gametype != GT_CTF) return CTF_FLAG_NONE; + + if (bs->inventory[INVENTORY_REDFLAG] > 0) return CTF_FLAG_RED; + else if (bs->inventory[INVENTORY_BLUEFLAG] > 0) return CTF_FLAG_BLUE; + return CTF_FLAG_NONE; +} + +/* +================== +BotCTFTeam +================== +*/ +int BotCTFTeam(bot_state_t *bs) { + char info[1024]; + + if (gametype != GT_CTF) return CTF_TEAM_NONE; + if (bs->client < 0 || bs->client >= MAX_CLIENTS) { + //BotAI_Print(PRT_ERROR, "BotCTFTeam: client out of range\n"); + return qfalse; + } + trap_GetConfigstring(CS_PLAYERS+bs->client, info, sizeof(info)); + // + if (atoi(Info_ValueForKey(info, "t")) == TEAM_RED) return CTF_TEAM_RED; + else if (atoi(Info_ValueForKey(info, "t")) == TEAM_BLUE) return CTF_TEAM_BLUE; + return CTF_TEAM_NONE; +} + +/* +================== +BotCTFRetreatGoals +================== +*/ +void BotCTFRetreatGoals(bot_state_t *bs) { + //when carrying a flag in ctf the bot should rush to the base + if (BotCTFCarryingFlag(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = trap_AAS_Time() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + } + } +} + +/* +================== +EntityIsDead +================== +*/ +qboolean EntityIsDead(aas_entityinfo_t *entinfo) { + playerState_t ps; + + if (entinfo->number >= 0 && entinfo->number < MAX_CLIENTS) { + //retrieve the current client state + BotAI_GetClientState( entinfo->number, &ps ); + if (ps.pm_type != PM_NORMAL) return qtrue; + } + return qfalse; +} + +/* +================== +EntityIsInvisible +================== +*/ +qboolean EntityIsInvisible(aas_entityinfo_t *entinfo) { + if (entinfo->powerups & (1 << PW_GHOST)) + { // 50% chance of being visible? + if (((unsigned int)(level.time)/1024)&0x01) // Every second or so, the bot will see the player, so he doesn't jitter. + { + return qtrue; + } + else + { + return qfalse; + } + } + else if (entinfo->powerups & (1 << PW_INVIS)) + { + return qtrue; + } + else if ( entinfo->flags & EF_NODRAW ) + { + return qtrue; + } + return qfalse; +} + +/* +================== +EntityCarriesFlag +================== +*/ +qboolean EntityCarriesFlag(aas_entityinfo_t *entinfo) { + /*if ( entinfo->powerups & ( 1 << PW_REDFLAG ) ) return qtrue;*/ + //if ( entinfo->powerups & ( 1 << PW_BORG_ADAPT ) ) return qtrue; + return qfalse; +} + +/* +================== +EntityIsShooting +================== +*/ +qboolean EntityIsShooting(aas_entityinfo_t *entinfo) { + if (entinfo->flags & EF_FIRING) { + return qtrue; + } + return qfalse; +} + +/* +================== +EntityIsChatting +================== +*/ +qboolean EntityIsChatting(aas_entityinfo_t *entinfo) { + if (entinfo->flags & EF_TALK) { + return qtrue; + } + return qfalse; +} + +/* +================== +EntityHasQuad +================== +*/ +qboolean EntityHasQuad(aas_entityinfo_t *entinfo) { + if (entinfo->powerups & (1 << PW_QUAD)) { + return qtrue; + } + return qfalse; +} + +/* +================== +BotCTFSeekGoals +================== +*/ +void BotCTFSeekGoals(bot_state_t *bs) { + float rnd; + int flagstatus, c; + + //when carrying a flag in ctf the bot should rush to the base + if (BotCTFCarryingFlag(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = trap_AAS_Time() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + } + else if (bs->rushbaseaway_time > trap_AAS_Time()) { + if (BotCTFTeam(bs) == CTF_TEAM_RED) flagstatus = bs->redflagstatus; + else flagstatus = bs->blueflagstatus; + //if the flag is back + if (flagstatus == 0) { + bs->rushbaseaway_time = 0; + } + } + return; + } + // + if (BotCTFTeam(bs) == CTF_TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus; + else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus; + //if the enemy flag is not at it's base + if (flagstatus == 1) { + //if Not defending the base + if (!(bs->ltgtype == LTG_DEFENDKEYAREA && + (bs->teamgoal.number == ctf_redflag.number || + bs->teamgoal.number == ctf_blueflag.number))) { + //if not already accompanying someone + if (bs->ltgtype != LTG_TEAMACCOMPANY) { + //if there is avisible team mate flag carrier + c = BotTeamFlagCarrierVisible(bs); + if (c >= 0) { + //follow the flag carrier + //the team mate + bs->teammate = c; + //last time the team mate was visible + bs->teammatevisible_time = trap_AAS_Time(); + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //get the team goal time + bs->teamgoal_time = trap_AAS_Time() + TEAM_ACCOMPANY_TIME; + bs->ltgtype = LTG_TEAMACCOMPANY; + bs->formation_dist = 3.5 * 32; //3.5 meter + bs->arrive_time = 0; + return; + } + } + } + } + //if the base flag is stolen + else if (flagstatus == 2) { + //if not already going for the enemy flag + if (bs->ltgtype != LTG_GETFLAG) { + //if there's no bot team leader + if (!BotTeamLeader(bs)) { + //go for the enemy flag + bs->ltgtype = LTG_GETFLAG; + //no team message + bs->teammessage_time = 1; + //set the time the bot will stop getting the flag + bs->teamgoal_time = trap_AAS_Time() + CTF_GETFLAG_TIME; + return; + } + } + } + //if both flags not at their bases + else if (flagstatus == 3) { + // + if (bs->ltgtype != LTG_GETFLAG && + bs->ltgtype != LTG_TEAMACCOMPANY) { + //if there is avisible team mate flag carrier + c = BotTeamFlagCarrierVisible(bs); + if (c >= 0) { + //follow the flag carrier + return; + } + else { + //otherwise attack the enemy base + } + return; + } + } + //if the bot is roaming + if (bs->ctfroam_time > trap_AAS_Time()) return; + //if already a CTF or team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_RETURNFLAG || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL) { + return; + } + //if the bot has anough aggression to decide what to do + if (BotAggression(bs) < 50) return; + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //get the flag or defend the base + rnd = random(); + if (rnd < 0.33 && ctf_redflag.areanum && ctf_blueflag.areanum) { + bs->ltgtype = LTG_GETFLAG; + //set the time the bot will stop getting the flag + bs->teamgoal_time = trap_AAS_Time() + CTF_GETFLAG_TIME; + } + else if (rnd < 0.66 && ctf_redflag.areanum && ctf_blueflag.areanum) { + // + if (BotCTFTeam(bs) == CTF_TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stops defending the base + bs->teamgoal_time = trap_AAS_Time() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + } + else { + bs->ltgtype = 0; + //set the time the bot will stop roaming + bs->ctfroam_time = trap_AAS_Time() + CTF_ROAM_TIME; + } +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +#endif //CTF + +/* +================== +BotPointAreaNum +================== +*/ +int BotPointAreaNum(vec3_t origin) { + int areanum, numareas, areas[10]; + vec3_t end; + + areanum = trap_AAS_PointAreaNum(origin); + if (areanum) return areanum; + VectorCopy(origin, end); + end[2] += 10; + numareas = trap_AAS_TraceAreas(origin, end, areas, NULL, 10); + if (numareas > 0) return areas[0]; + return 0; +} + +/* +================== +ClientName +================== +*/ +char *ClientName(int client, char *name, int size) { + char buf[MAX_INFO_STRING]; + + if (client < 0 || client >= MAX_CLIENTS) { + BotAI_Print(PRT_ERROR, "ClientName: client out of range\n"); + return "[client out of range]"; + } + trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf)); + strncpy(name, Info_ValueForKey(buf, "n"), size-1); + name[size-1] = '\0'; + Q_CleanStr( name ); + return name; +} + +/* +================== +ClientSkin +================== +*/ +char *ClientSkin(int client, char *skin, int size) { + char buf[MAX_INFO_STRING]; + + if (client < 0 || client >= MAX_CLIENTS) { + BotAI_Print(PRT_ERROR, "ClientSkin: client out of range\n"); + return "[client out of range]"; + } + trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf)); + strncpy(skin, Info_ValueForKey(buf, "model"), size-1); + skin[size-1] = '\0'; + return skin; +} + +/* +================== +ClientFromName +================== +*/ +int ClientFromName(char *name) { + int i; + char buf[MAX_INFO_STRING]; + static int maxclis; + + if (!maxclis) + maxclis = trap_Cvar_VariableIntegerValue("sv_maxclients"); + for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + Q_CleanStr( buf ); + if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i; + } + return -1; +} + +/* +================== +stristr +================== +*/ +char *stristr(char *str, char *charset) { + int i; + + while(*str) { + for (i = 0; charset[i] && str[i]; i++) { + if (toupper(charset[i]) != toupper(str[i])) break; + } + if (!charset[i]) return str; + str++; + } + return NULL; +} + +/* +================== +EasyClientName +================== +*/ +char *EasyClientName(int client, char *buf, int size) { + int i; + char *str1, *str2, *ptr, c; + char name[128]; + + strcpy(name, ClientName(client, name, sizeof(name))); + for (i = 0; name[i]; i++) name[i] &= 127; + //remove all spaces + for (ptr = strstr(name, " "); ptr; ptr = strstr(name, " ")) { + memmove(ptr, ptr+1, strlen(ptr+1)+1); + } + //check for [x] and ]x[ clan names + str1 = strstr(name, "["); + str2 = strstr(name, "]"); + if (str1 && str2) { + if (str2 > str1) memmove(str1, str2+1, strlen(str2+1)+1); + else memmove(str2, str1+1, strlen(str1+1)+1); + } + //remove Mr prefix + if ((name[0] == 'm' || name[0] == 'M') && + (name[1] == 'r' || name[1] == 'R')) { + memmove(name, name+2, strlen(name+2)+1); + } + //only allow lower case alphabet characters + ptr = name; + while(*ptr) { + c = *ptr; + if ((c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || c == '_') { + ptr++; + } + else if (c >= 'A' && c <= 'Z') { + *ptr += 'a' - 'A'; + ptr++; + } + else { + memmove(ptr, ptr+1, strlen(ptr + 1)+1); + } + } + strncpy(buf, name, size-1); + buf[size-1] = '\0'; + return buf; +} + +qboolean BotUseMeleeWeapon(bot_state_t *bs) { + if ( bs->inventory[ENEMY_HORIZONTAL_DIST] < 64 ) + { + /*if ( bs->cur_ps.persistant[PERS_CLASS] == PC_BORG || bs->cur_ps.persistant[PERS_CLASS] == PC_MEDIC ) + { + return qtrue; + }*/ + } + return qfalse; +} +/* +================== +BotChooseWeapon + + MCG - FIXME: This should really take into account: + Projectile vs. instant? + gravity on projectile? + Range to enemy vs range of weapon? + Some randomness on the weights? + +================== +*/ +void BotChooseWeapon(bot_state_t *bs) { + int newweaponnum; + + if (bs->cur_ps.weaponstate == WEAPON_RAISING || bs->cur_ps.weaponstate == WEAPON_DROPPING) + { + trap_EA_SelectWeapon(bs->client, bs->weaponnum); + } + else + { + newweaponnum = trap_BotChooseBestFightWeapon(bs->ws, bs->inventory, BotUseMeleeWeapon(bs)); + if (bs->weaponnum != newweaponnum) + { + bs->weaponchange_time = trap_AAS_Time(); + } + bs->weaponnum = newweaponnum; + //BotAI_Print(PRT_MESSAGE, "bs->weaponnum = %d\n", bs->weaponnum); + trap_EA_SelectWeapon(bs->client, bs->weaponnum); + } +} + +/* +================== +BotSetupForMovement +================== +*/ +void BotSetupForMovement(bot_state_t *bs) { + bot_initmove_t initmove; + + memset(&initmove, 0, sizeof(bot_initmove_t)); + VectorCopy(bs->cur_ps.origin, initmove.origin); + VectorCopy(bs->cur_ps.velocity, initmove.velocity); + VectorCopy(bs->cur_ps.origin, initmove.viewoffset); + initmove.viewoffset[2] += bs->cur_ps.viewheight; + initmove.entitynum = bs->entitynum; + initmove.client = bs->client; + initmove.thinktime = bs->thinktime; + //set the onground flag + if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) initmove.or_moveflags |= MFL_ONGROUND; + //set the teleported flag + if ((bs->cur_ps.pm_flags & PMF_TIME_KNOCKBACK) && (bs->cur_ps.pm_time > 0)) { + initmove.or_moveflags |= MFL_TELEPORTED; + } + //set the waterjump flag + if ((bs->cur_ps.pm_flags & PMF_TIME_WATERJUMP) && (bs->cur_ps.pm_time > 0)) { + initmove.or_moveflags |= MFL_WATERJUMP; + } + //set presence type + if (bs->cur_ps.pm_flags & PMF_DUCKED) initmove.presencetype = PRESENCE_CROUCH; + else initmove.presencetype = PRESENCE_NORMAL; + // + if (bs->walker > 0.5) initmove.or_moveflags |= MFL_WALK; + // + VectorCopy(bs->viewangles, initmove.viewangles); + // + trap_BotInitMoveState(bs->ms, &initmove); +} + +/* +================== +BotUpdateInventory +================== +*/ +void BotUpdateInventory(bot_state_t *bs) { + //armor + bs->inventory[INVENTORY_ARMOR] = bs->cur_ps.stats[STAT_ARMOR]; + + //weapons + bs->inventory[INVENTORY_GRENADELAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRENADE_LAUNCHER)) != 0; + bs->inventory[INVENTORY_STASIS] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_DISRUPTOR)) != 0; + bs->inventory[INVENTORY_PHASER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PHASER)) != 0; + bs->inventory[INVENTORY_DREADNOUGHT] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_DERMAL_REGEN)) != 0; + bs->inventory[INVENTORY_IMOD] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_NULL_HAND)) != 0; + bs->inventory[INVENTORY_COMPRESSION] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_COMPRESSION_RIFLE)) != 0; + bs->inventory[INVENTORY_TETRION] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_TR116)) != 0; + bs->inventory[INVENTORY_SCAVENGER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_COFFEE)) != 0; + bs->inventory[INVENTORY_QUANTUM] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_QUANTUM_BURST)) != 0; + + //ammo + bs->inventory[INVENTORY_GRENADES] = bs->cur_ps.ammo[WP_GRENADE_LAUNCHER]; + bs->inventory[INVENTORY_STASISAMMO] = bs->cur_ps.ammo[WP_DISRUPTOR]; + bs->inventory[INVENTORY_PHASERAMMO] = bs->cur_ps.ammo[WP_PHASER]; + bs->inventory[INVENTORY_DREADNOUGHTAMMO] = bs->cur_ps.ammo[WP_DERMAL_REGEN]; + bs->inventory[INVENTORY_IMODAMMO] = bs->cur_ps.ammo[WP_NULL_HAND]; + bs->inventory[INVENTORY_COMPRESSIONAMMO] = bs->cur_ps.ammo[WP_COMPRESSION_RIFLE]; + bs->inventory[INVENTORY_TETRIONAMMO] = bs->cur_ps.ammo[WP_TR116]; + bs->inventory[INVENTORY_SCAVENGERAMMO] = bs->cur_ps.ammo[WP_COFFEE]; + bs->inventory[INVENTORY_QUANTUMAMMO] = bs->cur_ps.ammo[WP_QUANTUM_BURST]; + + //powerups + bs->inventory[INVENTORY_HEALTH] = bs->cur_ps.stats[STAT_HEALTH]; + bs->inventory[INVENTORY_TRANSPORTER] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_TELEPORTER; + bs->inventory[INVENTORY_MEDKIT] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_MEDKIT; + bs->inventory[INVENTORY_QUAD] = bs->cur_ps.powerups[PW_QUAD] != 0; + bs->inventory[INVENTORY_ENVIRONMENTSUIT] = bs->cur_ps.powerups[PW_BOLTON] != 0; + bs->inventory[INVENTORY_HASTE] = bs->cur_ps.powerups[PW_HASTE] != 0; + bs->inventory[INVENTORY_INVISIBILITY] = bs->cur_ps.powerups[PW_INVIS] != 0; + bs->inventory[INVENTORY_REGEN] = bs->cur_ps.powerups[PW_LASER] != 0; + bs->inventory[INVENTORY_FLIGHT] = bs->cur_ps.powerups[PW_FLIGHT] != 0; + //bs->inventory[INVENTORY_REDFLAG] = bs->cur_ps.powerups[PW_REDFLAG] != 0; + //bs->inventory[INVENTORY_BLUEFLAG] = bs->cur_ps.powerups[PW_REDFLAG] != 0; + bs->inventory[INVENTORY_SEEKER] = bs->cur_ps.powerups[PW_FLASHLIGHT] != 0; + bs->inventory[INVENTORY_SHIELD] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_SHIELD; + bs->inventory[INVENTORY_DETPACK] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_DETPACK; + // +} + +/* +================== +BotUpdateBattleInventory +================== +*/ +void BotUpdateBattleInventory(bot_state_t *bs, int enemy) { + vec3_t dir; + aas_entityinfo_t entinfo; + + BotEntityInfo(enemy, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + bs->inventory[ENEMY_HEIGHT] = (int) dir[2]; + dir[2] = 0; + bs->inventory[ENEMY_HORIZONTAL_DIST] = (int) VectorLength(dir); + //FIXME: add num visible enemies and num visible team mates to the inventory +} + +/* +========================= +BotShouldDetonateDetPack +========================= +*/ +#define DETPACK_RADIUS 500 + +qboolean BotShouldDetonateDetPack(bot_state_t *bs) +{ + int botNum = 0, detWeight = 0; + vec3_t packOrg, dir; + float dist; + aas_entityinfo_t botinfo; + + // find the location of the DetPack + gentity_t *detpack = NULL; + char *classname = BG_FindClassnameForHoldable(HI_DETPACK); + + if (!classname) + { + return qfalse; + } + + //while ((detpack = G_Find (detpack, FOFS(classname), classname)) != NULL) + while ((detpack = G_Find (detpack, FOFS(classname), classname)) != NULL) + { + VectorCopy(detpack->r.currentOrigin, packOrg); + } + + // determine who would be killed in the blast radius + for (botNum = 0; botNum < MAX_CLIENTS; botNum++) + { + BotEntityInfo(botNum, &botinfo); + if (!botinfo.valid) continue; + + //calculate the distance towards the enemy + VectorSubtract(botinfo.origin, packOrg, dir); + dist = VectorLength(dir); + + if (dist < DETPACK_RADIUS) // bot would get caught in blast radius + { + if (BotSameTeam(bs, botNum)) // friendly casualty potential + { + if (botNum == bs->client) // suicide... bad + { + detWeight--; + } + if (EntityCarriesFlag(&botinfo)) // it's my teammate, and he's got the flag! + { + detWeight -= 11; + continue; + } + detWeight--; + } + else + { + if(EntityCarriesFlag(&botinfo)) // mwahaha + { + detWeight += 14; + } + detWeight++; + } + } + } + +// Com_Printf("detWeight %d\n", detWeight); + + if (detWeight > 0) + { + return qtrue; + } + return qfalse; +} + + + +/* +================== +BotBattleUseItems +================== +*/ +void BotBattleUseItems(bot_state_t *bs) { + + + if (bs->inventory[INVENTORY_DETPACK] > 0) + { + // this needs to be in two stages: placement and detonation + if (bs->ltgtype == LTG_DEFENDKEYAREA) + { + if (bs->inventory[INVENTORY_DETPACK_PLACED] == 0) // not placed yet + { + bs->inventory[INVENTORY_DETPACK_PLACED] = 1; + trap_EA_Use(bs->client); // place it + return; + } + } + + if (bs->inventory[INVENTORY_DETPACK_PLACED] == 1) // placed + { + if (BotShouldDetonateDetPack(bs)) // logic + { + bs->inventory[INVENTORY_DETPACK_PLACED] = 0; + trap_EA_Use(bs->client); // BOOM + return; + } + return; + } + return; + } + + if (bs->inventory[INVENTORY_SHIELD] > 0) + { + if (BotWantsToRetreat(bs) && (bs->inventory[INVENTORY_HEALTH] < 50)) + { + trap_EA_Use(bs->client); + } + return; + } + + if (bs->inventory[INVENTORY_TRANSPORTER] > 0) + { + if (!BotCTFCarryingFlag(bs) && (bs->inventory[INVENTORY_HEALTH] < 50)) + { + trap_EA_Use(bs->client); + return; + } + } + + if (bs->inventory[INVENTORY_MEDKIT] > 0) + { + if (bs->inventory[INVENTORY_HEALTH] < 30) + { + trap_EA_Use(bs->client); + } + return; + } +} + +/* +================== +BotSetTeleportTime +================== +*/ +void BotSetTeleportTime(bot_state_t *bs) { + if ((bs->cur_ps.eFlags ^ bs->last_eFlags) & EF_TELEPORT_BIT) { + bs->teleport_time = trap_AAS_Time(); + } + bs->last_eFlags = bs->cur_ps.eFlags; +} + +/* +================== +BotIsDead +================== +*/ +qboolean BotIsDead(bot_state_t *bs) { + return (bs->cur_ps.pm_type == PM_DEAD); +} + +/* +================== +BotIsObserver +================== +*/ +qboolean BotIsObserver(bot_state_t *bs) { + char buf[MAX_INFO_STRING]; + if (bs->cur_ps.pm_type == PM_SPECTATOR) return qtrue; + trap_GetConfigstring(CS_PLAYERS+bs->client, buf, sizeof(buf)); + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) return qtrue; + return qfalse; +} + +/* +================== +BotIntermission +================== +*/ +qboolean BotIntermission(bot_state_t *bs) { + //NOTE: we shouldn't be looking at the game code... + if (level.intermissiontime) return qtrue; + return (bs->cur_ps.pm_type == PM_FREEZE || bs->cur_ps.pm_type == PM_INTERMISSION); +} + +/* +================== +BotInLavaOrSlime +================== +*/ +qboolean BotInLavaOrSlime(bot_state_t *bs) { + vec3_t feet; + + VectorCopy(bs->origin, feet); + feet[2] -= 23; + return (trap_AAS_PointContents(feet) & (CONTENTS_LAVA|CONTENTS_SLIME)); +} + +/* +================== +BotCreateWayPoint +================== +*/ +bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum) { + bot_waypoint_t *wp; + vec3_t waypointmins = {-8, -8, -8}, waypointmaxs = {8, 8, 8}; + + wp = botai_freewaypoints; + if ( !wp ) { + BotAI_Print( PRT_WARNING, "BotCreateWayPoint: Out of waypoints\n" ); + return NULL; + } + botai_freewaypoints = botai_freewaypoints->next; + + Q_strncpyz( wp->name, name, sizeof(wp->name) ); + VectorCopy(origin, wp->goal.origin); + VectorCopy(waypointmins, wp->goal.mins); + VectorCopy(waypointmaxs, wp->goal.maxs); + wp->goal.areanum = areanum; + wp->next = NULL; + wp->prev = NULL; + return wp; +} + +/* +================== +BotFindWayPoint +================== +*/ +bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name) { + bot_waypoint_t *wp; + + for (wp = waypoints; wp; wp = wp->next) { + if (!Q_stricmp(wp->name, name)) return wp; + } + return NULL; +} + +/* +================== +BotFreeWaypoints +================== +*/ +void BotFreeWaypoints(bot_waypoint_t *wp) { + bot_waypoint_t *nextwp; + + for (; wp; wp = nextwp) { + nextwp = wp->next; + wp->next = botai_freewaypoints; + botai_freewaypoints = wp; + } +} + +/* +================== +BotInitWaypoints +================== +*/ +void BotInitWaypoints(void) { + int i; + + botai_freewaypoints = NULL; + for (i = 0; i < MAX_WAYPOINTS; i++) { + botai_waypoints[i].next = botai_freewaypoints; + botai_freewaypoints = &botai_waypoints[i]; + } +} + +/* +================== +TeamPlayIsOn +================== +*/ +int TeamPlayIsOn(void) { + return ( gametype == GT_TEAM || gametype == GT_CTF ); +} + +/* +================== +BotAggression +================== +*/ +float BotAggression(bot_state_t *bs) { + //if the bot has quad + if (bs->inventory[INVENTORY_QUAD]) { + //if the bot is not holding the gauntlet or the enemy is really nearby + if (bs->inventory[ENEMY_HORIZONTAL_DIST] < 80) { + return 70; + } + } + //if the enemy is located way higher than the bot + if (bs->inventory[ENEMY_HEIGHT] > 200) return 0; + //if the bot is very low on health + if (bs->inventory[INVENTORY_HEALTH] < 60) return 0; + //if the bot is low on health + if (bs->inventory[INVENTORY_HEALTH] < 80) { + //if the bot has insufficient armor + if (bs->inventory[INVENTORY_ARMOR] < 40) return 0; + } + + if (bs->inventory[INVENTORY_DREADNOUGHT] > 0 && + bs->inventory[INVENTORY_DREADNOUGHTAMMO] > 0) return 100; + + if (bs->inventory[INVENTORY_TETRION] > 0 && + bs->inventory[INVENTORY_TETRIONAMMO] > 0) return 95; + + if (bs->inventory[INVENTORY_QUANTUM] > 0 && + bs->inventory[INVENTORY_QUANTUMAMMO] > 0) return 90; + + if (bs->inventory[INVENTORY_STASIS] > 0 && + bs->inventory[INVENTORY_STASISAMMO] > 0) return 85; + + if (bs->inventory[INVENTORY_SCAVENGER] > 0 && + bs->inventory[INVENTORY_SCAVENGERAMMO] > 0) return 80; + + if (bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 && + bs->inventory[INVENTORY_GRENADES] > 0) return 75; + + if (bs->inventory[INVENTORY_IMOD] > 0 && + bs->inventory[INVENTORY_IMODAMMO] > 0) return 70; + + if (bs->inventory[INVENTORY_COMPRESSION] > 0 && + bs->inventory[INVENTORY_COMPRESSIONAMMO] > 0) return 65; + + //otherwise the bot is not feeling too aggressive + return 0; +} + +/* +================== +BotWantsToRetreat +================== +*/ +int BotWantsToRetreat(bot_state_t *bs) { + aas_entityinfo_t entinfo; + + //always retreat when carrying a CTF flag + if (BotCTFCarryingFlag(bs)) return qtrue; + // + if (bs->enemy >= 0) { + //if the enemy is carrying a flag + BotEntityInfo(bs->enemy, &entinfo); + if (EntityCarriesFlag(&entinfo)) return qfalse; + } + //if the bot is getting the flag + if (bs->ltgtype == LTG_GETFLAG) return qtrue; + // + if (BotAggression(bs) < 50) return qtrue; + return qfalse; +} + +/* +================== +BotWantsToChase +================== +*/ +int BotWantsToChase(bot_state_t *bs) { + aas_entityinfo_t entinfo; + + //always retreat when carrying a CTF flag + if (BotCTFCarryingFlag(bs)) return qfalse; + //if the enemy is carrying a flag + BotEntityInfo(bs->enemy, &entinfo); + if (EntityCarriesFlag(&entinfo)) return qtrue; + //if the bot is getting the flag + if (bs->ltgtype == LTG_GETFLAG) return qfalse; + // + if (BotAggression(bs) > 50) return qtrue; + return qfalse; +} + +/* +================== +BotWantsToHelp +================== +*/ +int BotWantsToHelp(bot_state_t *bs) { + return qtrue; +} + +/* +================== +BotCanAndWantsToRocketJump +================== +*/ +int BotCanAndWantsToRocketJump(bot_state_t *bs) { + float rocketjumper; + + //if rocket jumping is disabled + if (!bot_rocketjump.integer) return qfalse; + //if no rocket launcher + if (bs->inventory[INVENTORY_QUANTUM] <= 0) return qfalse; + //if low on rockets + if (bs->inventory[INVENTORY_QUANTUMAMMO] < 1) return qfalse; + //never rocket jump with the Quad + if (bs->inventory[INVENTORY_QUAD]) + { + if ( rpg_selfdamage.integer != 0 ) + { + return qfalse; + } + } + //if low on health + if (bs->inventory[INVENTORY_HEALTH] < 50) + { //if not full health + if ( rpg_selfdamage.integer != 0 ) + { + return qfalse; + } + } + if (bs->inventory[INVENTORY_HEALTH] < 60) + { //if the bot has insufficient armor + if (bs->inventory[INVENTORY_ARMOR] < 20) + { + if ( rpg_selfdamage.integer != 0 ) + { + return qfalse; + } + } + } + rocketjumper = 1; + return qtrue; +} + +/* +================== +BotGoCamp +================== +*/ +void BotGoCamp(bot_state_t *bs, bot_goal_t *goal) { + float camper; + + //set message time to zero so bot will NOT show any message + bs->teammessage_time = 0; + //set the ltg type + bs->ltgtype = LTG_CAMP; + //set the team goal + memcpy(&bs->teamgoal, goal, sizeof(bot_goal_t)); + //get the team goal time + camper = 0; + if (camper > 0.99) bs->teamgoal_time = 99999; + else bs->teamgoal_time = 120 + 180 * camper + random() * 15; + //set the last time the bot started camping + bs->camp_time = trap_AAS_Time(); + //the teammate that requested the camping + bs->teammate = 0; + //do NOT type arrive message + bs->arrive_time = 1; +} + +/* +================== +BotWantsToCamp +================== +*/ +int BotWantsToCamp(bot_state_t *bs) { + float camper; + camper = 0; + return qfalse; +} + +/* +================== +BotDontAvoid +================== +*/ +void BotDontAvoid(bot_state_t *bs, char *itemname) { + bot_goal_t goal; + int num; + + num = trap_BotGetLevelItemGoal(-1, itemname, &goal); + while(num >= 0) { + trap_BotRemoveFromAvoidGoals(bs->gs, goal.number); + num = trap_BotGetLevelItemGoal(num, itemname, &goal); + } +} + +/* +================== +BotGoForPowerups +================== +*/ +void BotGoForPowerups(bot_state_t *bs) { + + //don't avoid any of the powerups anymore + BotDontAvoid(bs, "Quantum Weapon Enhancer"); + BotDontAvoid(bs, "Nano-Regenerative Protoplasmer"); + BotDontAvoid(bs, "Metaphasic Shielding"); + BotDontAvoid(bs, "Temporal Accelerator"); + BotDontAvoid(bs, "Personal Cloaking Device"); + BotDontAvoid(bs, "Seeker Drone"); + BotDontAvoid(bs, "Anti-Gravity Pack"); + //reset the long term goal time so the bot will go for the powerup + //NOTE: the long term goal type doesn't change + bs->ltg_time = 0; +} + +/* +================== +BotRoamGoal +================== +*/ +void BotRoamGoal(bot_state_t *bs, vec3_t goal) { + int pc, i; + float len, rnd; + vec3_t dir, bestorg, belowbestorg; + bsp_trace_t trace; + + for (i = 0; i < 10; i++) { + //start at the bot origin + VectorCopy(bs->origin, bestorg); + rnd = random(); + if (rnd > 0.25) { + //add a random value to the x-coordinate + if (random() < 0.5) bestorg[0] -= 800 * random() + 100; + else bestorg[0] += 800 * random() + 100; + } + if (rnd < 0.75) { + //add a random value to the y-coordinate + if (random() < 0.5) bestorg[1] -= 800 * random() + 100; + else bestorg[1] += 800 * random() + 100; + } + //add a random value to the z-coordinate (NOTE: 48 = maxjump?) + bestorg[2] += 2 * 48 * crandom(); + //trace a line from the origin to the roam target + BotAI_Trace(&trace, bs->origin, NULL, NULL, bestorg, bs->entitynum, MASK_SOLID); + //direction and length towards the roam target + VectorSubtract(trace.endpos, bs->origin, dir); + len = VectorNormalize(dir); + //if the roam target is far away anough + if (len > 200) { + //the roam target is in the given direction before walls + VectorScale(dir, len * trace.fraction - 40, dir); + VectorAdd(bs->origin, dir, bestorg); + //get the coordinates of the floor below the roam target + belowbestorg[0] = bestorg[0]; + belowbestorg[1] = bestorg[1]; + belowbestorg[2] = bestorg[2] - 800; + BotAI_Trace(&trace, bestorg, NULL, NULL, belowbestorg, bs->entitynum, MASK_SOLID); + // + if (!trace.startsolid) { + trace.endpos[2]++; + pc = trap_PointContents(trace.endpos, bs->entitynum); + if (!(pc & (CONTENTS_LAVA | CONTENTS_SLIME))) { + VectorCopy(bestorg, goal); + return; + } + } + } + } + VectorCopy(bestorg, goal); +} + +/* +================== +BotAttackMove +================== +*/ +bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl) { + int movetype, i; + float attack_skill, jumper, croucher, dist, strafechange_time; + float attack_dist, attack_range; + vec3_t forward, backward, sideward, hordir, up = {0, 0, 1}; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + bot_goal_t goal; + + if (bs->attackchase_time > trap_AAS_Time()) { + //create the chase goal + goal.entitynum = bs->enemy; + goal.areanum = bs->lastenemyareanum; + VectorCopy(bs->lastenemyorigin, goal.origin); + VectorSet(goal.mins, -8, -8, -8); + VectorSet(goal.maxs, 8, 8, 8); + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, tfl); + return moveresult; + } + // + memset(&moveresult, 0, sizeof(bot_moveresult_t)); + // + attack_skill = 1; + jumper = 1; + croucher = 1; + //initialize the movement state + BotSetupForMovement(bs); + //get the enemy entity info + BotEntityInfo(bs->enemy, &entinfo); + //direction towards the enemy + VectorSubtract(entinfo.origin, bs->origin, forward); + //the distance towards the enemy + dist = VectorNormalize(forward); + VectorNegate(forward, backward); + //walk, crouch or jump + movetype = MOVE_WALK; + // + if (bs->attackcrouch_time < trap_AAS_Time() - 1) { + if (random() < jumper) { + movetype = MOVE_JUMP; + } + //wait at least one second before crouching again + else if (bs->attackcrouch_time < trap_AAS_Time() - 1 && random() < croucher) { + bs->attackcrouch_time = trap_AAS_Time() + croucher * 5; + } + } + if (bs->attackcrouch_time > trap_AAS_Time()) movetype = MOVE_CROUCH; + //if the bot should jump + if (movetype == MOVE_JUMP) { + //if jumped last frame + if (bs->attackjump_time > trap_AAS_Time()) { + movetype = MOVE_WALK; + } + else { + bs->attackjump_time = trap_AAS_Time() + 1; + } + } + + //if using assimilator or alt-fire hypo... + if ( bs->weaponnum == WP_TOOLKIT || bs->weaponnum == (WP_VOYAGER_HYPO+WP_NUM_WEAPONS) ) + {//get real close + attack_dist = 16; + attack_range = 16; + } + else + { + attack_dist = IDEAL_ATTACKDIST; + attack_range = 40; + } + + //increase the strafe time + bs->attackstrafe_time += bs->thinktime; + //get the strafe change time + strafechange_time = 0.4 + (1 - attack_skill) * 0.2; + if (attack_skill > 0.7) strafechange_time += crandom() * 0.2; + //if the strafe direction should be changed + if (bs->attackstrafe_time > strafechange_time) { + //some magic number :) + if (random() > 0.935) { + //flip the strafe direction + bs->flags ^= BFL_STRAFERIGHT; + bs->attackstrafe_time = 0; + } + } + // + for (i = 0; i < 2; i++) { + hordir[0] = forward[0]; + hordir[1] = forward[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //get the sideward vector + CrossProduct(hordir, up, sideward); + //reverse the vector depending on the strafe direction + if (bs->flags & BFL_STRAFERIGHT) VectorNegate(sideward, sideward); + //randomly go back a little + if (random() > 0.9) { + VectorAdd(sideward, backward, sideward); + } + else { + //walk forward or backward to get at the ideal attack distance + if (dist > attack_dist + attack_range) VectorAdd(sideward, forward, sideward); + else if (dist < attack_dist - attack_range) VectorAdd(sideward, backward, sideward); + } + //perform the movement + if (trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) return moveresult; + //movement failed, flip the strafe direction + bs->flags ^= BFL_STRAFERIGHT; + bs->attackstrafe_time = 0; + } + //bot couldn't do any usefull movement +// bs->attackchase_time = AAS_Time() + 6; + return moveresult; +} + +/* +================== +BotSameTeam +================== +*/ +int BotSameTeam(bot_state_t *bs, int entnum) { + char info1[1024], info2[1024]; + + if (bs->client < 0 || bs->client >= MAX_CLIENTS) { + //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); + return qfalse; + } + if (entnum < 0 || entnum >= MAX_CLIENTS) { + //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); + return qfalse; + } + if (gametype == GT_TEAM || gametype == GT_CTF) { + trap_GetConfigstring(CS_PLAYERS+bs->client, info1, sizeof(info1)); + trap_GetConfigstring(CS_PLAYERS+entnum, info2, sizeof(info2)); + // + if (atoi(Info_ValueForKey(info1, "t")) == atoi(Info_ValueForKey(info2, "t"))) return qtrue; + } + return qfalse; +} + +/* +================== +InFieldOfVision +================== +*/ +qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles) +{ + int i; + float diff, angle; + + for (i = 0; i < 2; i++) { + angle = AngleMod(viewangles[i]); + angles[i] = AngleMod(angles[i]); + diff = angles[i] - angle; + if (angles[i] > angle) { + if (diff > 180.0) diff -= 360.0; + } + else { + if (diff < -180.0) diff += 360.0; + } + if (diff > 0) { + if (diff > fov * 0.5) return qfalse; + } + else { + if (diff < -fov * 0.5) return qfalse; + } + } + return qtrue; +} + +/* +================== +BotEntityVisible + +returns visibility in the range [0, 1] taking fog and water surfaces into account +================== +*/ +float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent) { + int i, contents_mask, passent, hitent, infog, inwater, otherinfog, pc; + float fogdist, waterfactor, vis, bestvis; + bsp_trace_t trace; + aas_entityinfo_t entinfo; + vec3_t dir, entangles, start, end, middle; + + //calculate middle of bounding box + BotEntityInfo(ent, &entinfo); + VectorAdd(entinfo.mins, entinfo.maxs, middle); + VectorScale(middle, 0.5, middle); + VectorAdd(entinfo.origin, middle, middle); + //check if entity is within field of vision + VectorSubtract(middle, eye, dir); + vectoangles(dir, entangles); + if (!InFieldOfVision(viewangles, fov, entangles)) return 0; + // + pc = trap_AAS_PointContents(eye); + infog = (pc & CONTENTS_SOLID); + inwater = (pc & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)); + // + bestvis = 0; + for (i = 0; i < 3; i++) { + //if the point is not in potential visible sight + //if (!AAS_inPVS(eye, middle)) continue; + // + contents_mask = CONTENTS_SOLID|CONTENTS_PLAYERCLIP; + passent = viewer; + hitent = ent; + VectorCopy(eye, start); + VectorCopy(middle, end); + //if the entity is in water, lava or slime + if (trap_AAS_PointContents(middle) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) { + contents_mask |= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); + } + //if eye is in water, lava or slime + if (inwater) { + if (!(contents_mask & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) { + passent = ent; + hitent = viewer; + VectorCopy(middle, start); + VectorCopy(eye, end); + } + contents_mask ^= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); + } + //trace from start to end + BotAI_Trace(&trace, start, NULL, NULL, end, passent, contents_mask); + //if water was hit + waterfactor = 1.0; + if (trace.contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) { + //if the water surface is translucent + if (1) { + //trace through the water + contents_mask &= ~(CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); + BotAI_Trace(&trace, trace.endpos, NULL, NULL, end, passent, contents_mask); + waterfactor = 0.5; + } + } + //if a full trace or the hitent was hit + if (trace.fraction >= 1 || trace.ent == hitent) { + //check for fog, assuming there's only one fog brush where + //either the viewer or the entity is in or both are in + otherinfog = (trap_AAS_PointContents(middle) & CONTENTS_FOG); + if (infog && otherinfog) { + VectorSubtract(trace.endpos, eye, dir); + fogdist = VectorLength(dir); + } + else if (infog) { + VectorCopy(trace.endpos, start); + BotAI_Trace(&trace, start, NULL, NULL, eye, viewer, CONTENTS_FOG); + VectorSubtract(eye, trace.endpos, dir); + fogdist = VectorLength(dir); + } + else if (otherinfog) { + VectorCopy(trace.endpos, end); + BotAI_Trace(&trace, eye, NULL, NULL, end, viewer, CONTENTS_FOG); + VectorSubtract(end, trace.endpos, dir); + fogdist = VectorLength(dir); + } + else { + //if the entity and the viewer are not in fog assume there's no fog in between + fogdist = 0; + } + //decrease visibility with the view distance through fog + vis = 1 / ((fogdist * fogdist * 0.001) < 1 ? 1 : (fogdist * fogdist * 0.001)); + //if entering water visibility is reduced + vis *= waterfactor; + // + if (vis > bestvis) bestvis = vis; + //if pretty much no fog + if (bestvis >= 0.95) return bestvis; + } + //check bottom and top of bounding box as well + if (i == 0) middle[2] += entinfo.mins[2]; + else if (i == 1) middle[2] += entinfo.maxs[2] - entinfo.mins[2]; + } + return bestvis; +} + +/* +================== +BotFindEnemy +================== +*/ +int BotFindEnemy(bot_state_t *bs, int curenemy) { + int i, healthdecrease; + float f, dist, curdist, alertness, easyfragger, vis; + aas_entityinfo_t entinfo, curenemyinfo; + vec3_t dir, angles; + + alertness = 1; + easyfragger = 1; + //check if the health decreased + healthdecrease = bs->lasthealth > bs->inventory[INVENTORY_HEALTH]; + //remember the current health value + bs->lasthealth = bs->inventory[INVENTORY_HEALTH]; + // + if (curenemy >= 0) { + BotEntityInfo(curenemy, &curenemyinfo); + if (EntityCarriesFlag(&curenemyinfo)) return qfalse; + VectorSubtract(curenemyinfo.origin, bs->origin, dir); + curdist = VectorLength(dir); + } + else { + curdist = 0; + } + // + + //FIXME: This only finds lowest numbered enemy, not the closest or best!!! + // 6/15/00 dpk changed this + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + + if (i == bs->client) continue; + //if it's the current enemy + if (i == curenemy) continue; + // + BotEntityInfo(i, &entinfo); + // + if (!entinfo.valid) continue; + //if on the same team + if (BotSameTeam(bs, i)) continue; + //if the enemy isn't dead and the enemy isn't the bot self + if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; + //if the enemy is invisible and not shooting + if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { + continue; + } + //if not an easy fragger don't shoot at chatting players + if (easyfragger < 0.5 && EntityIsChatting(&entinfo)) continue; + // + if (lastteleport_time > trap_AAS_Time() - 3) { + VectorSubtract(entinfo.origin, lastteleport_origin, dir); + if (VectorLength(dir) < 70) continue; + } + //calculate the distance towards the enemy + VectorSubtract(entinfo.origin, bs->origin, dir); + dist = VectorLength(dir); + + + //if this entity is not carrying a flag + if (!EntityCarriesFlag(&entinfo)) +// if (EntityCarriesFlag(&entinfo)) +/* { + // pick this one! + } + else +*/ { + //if this enemy is further away than the current one + if (curenemy >= 0 && dist > curdist) continue; + } + + //if the bot in too far away for me to notice + if (dist > 900 + alertness * 4000) continue; + + + //if the bot's health decreased or the enemy is shooting + if (curenemy < 0 && (healthdecrease || EntityIsShooting(&entinfo))) f = 360; + else f = 90 + 90 - (90 - (dist > 810 ? 810 : dist) / 9); + //check if the enemy is visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, f, i); + if (vis <= 0) continue; + //if the enemy is quite far away, not shooting and the bot is not damaged + if (curenemy < 0 && dist > 200 && !healthdecrease && !EntityIsShooting(&entinfo)) + { + //check if we can avoid this enemy + VectorSubtract(bs->origin, entinfo.origin, dir); + vectoangles(dir, angles); + //if the bot isn't in the fov of the enemy + if (!InFieldOfVision(entinfo.angles, 120, angles)) { + //update some stuff for this enemy + BotUpdateBattleInventory(bs, i); + //if the bot doesn't really want to fight + if (BotWantsToRetreat(bs)) continue; + } + } +// } + //found an enemy + bs->enemy = entinfo.number; + if (curenemy >= 0) bs->enemysight_time = trap_AAS_Time() - 2; + else bs->enemysight_time = trap_AAS_Time(); + bs->enemysuicide = qfalse; + bs->enemydeath_time = 0; + return qtrue; + } + return qfalse; +} + +/* +================== +BotTeamFlagCarrierVisible +================== +*/ +int BotTeamFlagCarrierVisible(bot_state_t *bs) { + int i; + float vis; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) continue; + //if this player is carrying a flag + if (!EntityCarriesFlag(&entinfo)) continue; + //if the flag carrier is not on the same team + if (!BotSameTeam(bs, i)) continue; + //if the flag carrier is not visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis <= 0) continue; + // + return i; + } + return -1; +} + +/* +================== +BotAimAtEnemy + + MCG - FIXME: This is not set up at all correctly in the following areas: + Needs to consider if weapon is an alt weapon for aim and accuracy as well as functionality + Need to consider range? + Grenade Primary and alt as well as Scavenger alt need to take into account gravity + Needs to pick our new weapons correctly to determine which ones they should: + lead with (projectiles have speed) + decay aim purposely (instant hit weaps) + shoot "around corners" (bouncers, mines, splashdamage?) + +================== +*/ +void BotAimAtEnemy(bot_state_t *bs) { + int i, enemyvisible; + float dist, f, aim_skill, aim_accuracy, speed, reactiontime; + vec3_t dir, bestorigin, end, start, groundtarget, cmdmove, enemyvelocity; + vec3_t mins = {-4,-4,-4}, maxs = {4, 4, 4}; + weaponinfo_t wi; + aas_entityinfo_t entinfo; + bot_goal_t goal; + bsp_trace_t trace; + vec3_t target; + + //if the bot has no enemy + if (bs->enemy < 0) return; + // + //BotAI_Print(PRT_MESSAGE, "client %d: aiming at client %d\n", bs->entitynum, bs->enemy); + // + aim_skill = 1; + aim_accuracy = 1; + // + if (aim_skill > 0.95) + { + //don't aim too early + reactiontime = 0.5; + if (bs->enemysight_time > trap_AAS_Time() - reactiontime) return; + if (bs->teleport_time > trap_AAS_Time() - reactiontime) return; + } + + //get the weapon information + trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi); + //get the weapon specific aim accuracy and or aim skill + + if (wi.number == WP_GRENADE_LAUNCHER) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER, 0, 1); + aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER, 0, 1); + } + if (wi.number == WP_DISRUPTOR) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_STASIS, 0, 1); + aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_STASIS, 0, 1); + } + if (wi.number == WP_PHASER) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_PHASER, 0, 1); + } + if (wi.number == WP_NULL_HAND) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_IMOD, 0, 1); + } + if (wi.number == WP_COMPRESSION_RIFLE) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_COMPRESSION, 0, 1); + } + if (wi.number == WP_TR116) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_TETRION, 0, 1); + } + if (wi.number == WP_DERMAL_REGEN) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_DREADNOUGHT, 0, 1); + } + if (wi.number == WP_QUANTUM_BURST) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_QUANTUM, 0, 1); + aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_QUANTUM, 0, 1); + } + if (wi.number == WP_COFFEE) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_SCAVENGER, 0, 1); + } + + // + if (aim_accuracy <= 0) aim_accuracy = 0.0001; + //get the enemy entity information + BotEntityInfo(bs->enemy, &entinfo); + //if the enemy is invisible then shoot crappy most of the time + if (EntityIsInvisible(&entinfo)) { + if (random() > 0.1) aim_accuracy *= 0.4; + } + // + VectorSubtract(entinfo.origin, entinfo.lastvisorigin, enemyvelocity); + VectorScale(enemyvelocity, 1 / entinfo.update_time, enemyvelocity); + //enemy origin and velocity is remembered every 0.5 seconds + if (bs->enemyposition_time < trap_AAS_Time()) { + // + bs->enemyposition_time = trap_AAS_Time() + 0.5; + VectorCopy(enemyvelocity, bs->enemyvelocity); + VectorCopy(entinfo.origin, bs->enemyorigin); + } + //if not extremely skilled + if (aim_skill < 0.9) { + VectorSubtract(entinfo.origin, bs->enemyorigin, dir); + //if the enemy moved a bit + if (VectorLength(dir) > 48) { + //if the enemy changed direction + if (DotProduct(bs->enemyvelocity, enemyvelocity) < 0) { + //aim accuracy should be worse now + aim_accuracy *= 0.7; + } + } + } + //check visibility of enemy + enemyvisible = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy); + //if the enemy is visible + if (enemyvisible) { + // + VectorCopy(entinfo.origin, bestorigin); + bestorigin[2] += 8; + //get the start point shooting from + //NOTE: the x and y projectile start offsets are ignored + VectorCopy(bs->origin, start); + start[2] += bs->cur_ps.viewheight; + start[2] += wi.offset[2]; + // + BotAI_Trace(&trace, start, mins, maxs, bestorigin, bs->entitynum, MASK_SHOT); + //if the enemy is NOT hit + if (trace.fraction <= 1 && trace.ent != entinfo.number) { + bestorigin[2] += 16; + } + //if it is not an instant hit weapon the bot might want to predict the enemy + if (wi.speed) { + // + VectorSubtract(bestorigin, bs->origin, dir); + dist = VectorLength(dir); + VectorSubtract(entinfo.origin, bs->enemyorigin, dir); + //if the enemy is NOT pretty far away and strafing just small steps left and right + if (!(dist > 100 && VectorLength(dir) < 32)) { + //if skilled anough do exact prediction + if (aim_skill > 0.8 && + //if the weapon is ready to fire + bs->cur_ps.weaponstate == WEAPON_READY) { + aas_clientmove_t move; + vec3_t origin; + + VectorSubtract(entinfo.origin, bs->origin, dir); + //distance towards the enemy + dist = VectorLength(dir); + //direction the enemy is moving in + VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); + // + VectorScale(dir, 1 / entinfo.update_time, dir); + // + VectorCopy(entinfo.origin, origin); + origin[2] += 1; + // + VectorClear(cmdmove); + //AAS_ClearShownDebugLines(); + trap_AAS_PredictClientMovement(&move, bs->enemy, origin, + PRESENCE_CROUCH, qfalse, + dir, cmdmove, 0, + dist * 10 / wi.speed, 0.1, 0, 0, qfalse); + VectorCopy(move.endpos, bestorigin); + //BotAI_Print(PRT_MESSAGE, "%1.1f predicted speed = %f, frames = %f\n", trap_AAS_Time(), VectorLength(dir), dist * 10 / wi.speed); + } + //if not that skilled do linear prediction + else if (aim_skill > 0.4) { + VectorSubtract(entinfo.origin, bs->origin, dir); + //distance towards the enemy + dist = VectorLength(dir); + //direction the enemy is moving in + VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); + dir[2] = 0; + // + speed = VectorNormalize(dir) / entinfo.update_time; + //botimport.Print(PRT_MESSAGE, "speed = %f, wi->speed = %f\n", speed, wi->speed); + //best spot to aim at + VectorMA(entinfo.origin, (dist / wi.speed) * speed, dir, bestorigin); + } + } + } + //if the projectile does radial damage + if (aim_skill > 0.6 && wi.proj.damagetype & DAMAGETYPE_RADIAL) { + //if the enemy isn't standing significantly higher than the bot + if (entinfo.origin[2] < bs->origin[2] + 16) { + //try to aim at the ground in front of the enemy + VectorCopy(entinfo.origin, end); + end[2] -= 64; + BotAI_Trace(&trace, entinfo.origin, NULL, NULL, end, entinfo.number, MASK_SHOT); + // + VectorCopy(bestorigin, groundtarget); + if (trace.startsolid) groundtarget[2] = entinfo.origin[2] - 16; + else groundtarget[2] = trace.endpos[2] - 8; + //trace a line from projectile start to ground target + BotAI_Trace(&trace, start, NULL, NULL, groundtarget, bs->entitynum, MASK_SHOT); + //if hitpoint is not vertically too far from the ground target + if (fabs(trace.endpos[2] - groundtarget[2]) < 50) { + VectorSubtract(trace.endpos, groundtarget, dir); + //if the hitpoint is near anough the ground target + if (VectorLength(dir) < 60) { + VectorSubtract(trace.endpos, start, dir); + //if the hitpoint is far anough from the bot + if (VectorLength(dir) > 100) { + //check if the bot is visible from the ground target + trace.endpos[2] += 1; + BotAI_Trace(&trace, trace.endpos, NULL, NULL, entinfo.origin, entinfo.number, MASK_SHOT); + if (trace.fraction >= 1) { + //botimport.Print(PRT_MESSAGE, "%1.1f aiming at ground\n", AAS_Time()); + VectorCopy(groundtarget, bestorigin); + } + } + } + } + } + } + bestorigin[0] += 20 * crandom() * (1 - aim_accuracy); + bestorigin[1] += 20 * crandom() * (1 - aim_accuracy); + bestorigin[2] += 10 * crandom() * (1 - aim_accuracy); + } + else { + // + VectorCopy(bs->lastenemyorigin, bestorigin); + bestorigin[2] += 8; + //if the bot is skilled anough + if (aim_skill > 0.5) { + //do prediction shots around corners + if (wi.number == WP_DISRUPTOR || + wi.number == WP_GRENADE_LAUNCHER) + { + //create the chase goal + goal.entitynum = bs->client; + goal.areanum = bs->areanum; + VectorCopy(bs->eye, goal.origin); + VectorSet(goal.mins, -8, -8, -8); + VectorSet(goal.maxs, 8, 8, 8); + // + if (trap_BotPredictVisiblePosition(bs->lastenemyorigin, bs->lastenemyareanum, &goal, TFL_DEFAULT, target)) { + VectorSubtract(target, bs->eye, dir); + if (VectorLength(dir) > 80) { + VectorCopy(target, bestorigin); + bestorigin[2] -= 20; + } + } + aim_accuracy = 1; + } + } + } + // + if (enemyvisible) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, bestorigin, bs->entitynum, MASK_SHOT); + VectorCopy(trace.endpos, bs->aimtarget); + } + else { + VectorCopy(bestorigin, bs->aimtarget); + } + //get aim direction + VectorSubtract(bestorigin, bs->eye, dir); + // kef -- fixme. i'm guessing this is listing all of the instant-hit weapons? + if (wi.number == WP_PHASER || + wi.number == WP_NULL_HAND) { + //distance towards the enemy + dist = VectorLength(dir); + if (dist > 150) dist = 150; + f = 0.6 + dist / 150 * 0.4; + aim_accuracy *= f; + } + //add some random stuff to the aim direction depending on the aim accuracy + if (aim_accuracy < 0.8) { + VectorNormalize(dir); + for (i = 0; i < 3; i++) dir[i] += 0.3 * crandom() * (1 - aim_accuracy); + } + //set the ideal view angles + vectoangles(dir, bs->ideal_viewangles); + //take the weapon spread into account for lower skilled bots + bs->ideal_viewangles[PITCH] += 6 * wi.vspread * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]); + bs->ideal_viewangles[YAW] += 6 * wi.hspread * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]); + //if the bots should be really challenging + //if the bot is really accurate and has the enemy in view for some time + if (aim_accuracy > 0.9 && bs->enemysight_time < trap_AAS_Time() - 1) + { + //set the view angles directly + if (bs->ideal_viewangles[PITCH] > 180) + { + bs->ideal_viewangles[PITCH] -= 360; + } + VectorCopy(bs->ideal_viewangles, bs->viewangles); + trap_EA_View(bs->client, bs->viewangles); + } +} + +/* +================== +BotCheckAttack +================== +*/ +void BotCheckAttack(bot_state_t *bs) { + float points, reactiontime, fov, firethrottle; + bsp_trace_t bsptrace; + //float selfpreservation; + vec3_t forward, right, start, end, dir, angles; + weaponinfo_t wi; + bsp_trace_t trace; + aas_entityinfo_t entinfo; + vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; + + if (bs->enemy < 0) return; + // + reactiontime = 0; + if (bs->enemysight_time > trap_AAS_Time() - reactiontime) return; + if (bs->teleport_time > trap_AAS_Time() - reactiontime) return; + //if changing weapons + if (bs->weaponchange_time > trap_AAS_Time() - 0.1) return; + //check fire throttle characteristic + if (bs->firethrottlewait_time > trap_AAS_Time()) return; + firethrottle = 1; + if (bs->firethrottleshoot_time < trap_AAS_Time()) { + if (random() > firethrottle) { + bs->firethrottlewait_time = trap_AAS_Time() + firethrottle; + bs->firethrottleshoot_time = 0; + } + else { + bs->firethrottleshoot_time = trap_AAS_Time() + 1 - firethrottle; + bs->firethrottlewait_time = 0; + } + } + // + BotEntityInfo(bs->enemy, &entinfo); + VectorSubtract(entinfo.origin, bs->eye, dir); + // + if (VectorLength(dir) < 100) fov = 120; + else fov = 50; + /* + //if the enemy isn't visible + if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, fov, bs->enemy)) { + //botimport.Print(PRT_MESSAGE, "enemy not visible\n"); + return; + }*/ + vectoangles(dir, angles); + if (!InFieldOfVision(bs->viewangles, fov, angles)) return; + BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->aimtarget, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (bsptrace.fraction < 1 && bsptrace.ent != bs->enemy) return; + + //get the weapon info + trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi); + //get the start point shooting from + VectorCopy(bs->origin, start); + start[2] += bs->cur_ps.viewheight; + AngleVectors(bs->viewangles, forward, right, NULL); + start[0] += forward[0] * wi.offset[0] + right[0] * wi.offset[1]; + start[1] += forward[1] * wi.offset[0] + right[1] * wi.offset[1]; + start[2] += forward[2] * wi.offset[0] + right[2] * wi.offset[1] + wi.offset[2]; + //end point aiming at + VectorMA(start, 1000, forward, end); + //a little back to make sure not inside a very close enemy + VectorMA(start, -12, forward, start); + BotAI_Trace(&trace, start, mins, maxs, end, bs->entitynum, MASK_SHOT); + //if won't hit the enemy + if (trace.ent != bs->enemy) { + //if the entity is a client + if (trace.ent > 0 && trace.ent <= MAX_CLIENTS) { + //if a teammate is hit + if (BotSameTeam(bs, trace.ent)) return; + } + //if the projectile does a radial damage + if (wi.proj.damagetype & DAMAGETYPE_RADIAL) { + if (trace.fraction * 1000 < wi.proj.radius) { + points = (wi.proj.damage - 0.5 * trace.fraction * 1000) * 0.5; + if (points > 0) { +// selfpreservation = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_SELFPRESERVATION, 0, 1); +// if (random() < selfpreservation) return; + return; + } + } + //FIXME: check if a teammate gets radial damage + } + } + //if fire has to be release to activate weapon + if (wi.flags & WFL_FIRERELEASED) { + if (bs->flags & BFL_ATTACKED) + {// check in here to either call the alt_fire or not!! also, adjust the bots weapon... hackery? + if (bs->weaponnum > WP_NUM_WEAPONS) // it's an alt_fire! + { + bs->weaponnum -= WP_NUM_WEAPONS; + trap_EA_Alt_Attack(bs->client); + } + else + { + trap_EA_Attack(bs->client); + } + } + } + else + { // check in here to either call the alt_fire or not!! also, adjust the bots weapon... hackery? + if (bs->weaponnum > WP_NUM_WEAPONS) // it's an alt_fire! + { + bs->weaponnum -= WP_NUM_WEAPONS; + trap_EA_Alt_Attack(bs->client); + } + else + { + trap_EA_Attack(bs->client); + } + } + bs->flags ^= BFL_ATTACKED; +} + +/* +================== +BotMapScripts +================== +*/ +void BotMapScripts(bot_state_t *bs) { + char info[1024]; + char mapname[128]; + int i, shootbutton; + float aim_accuracy; + aas_entityinfo_t entinfo; + vec3_t dir; + + trap_GetServerinfo(info, sizeof(info)); + + strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1); + mapname[sizeof(mapname)-1] = '\0'; + + if (!Q_stricmp(mapname, "q3tourney6")) { + vec3_t mins = {700, 204, 672}, maxs = {964, 468, 680}; + vec3_t buttonorg = {304, 352, 920}; + //NOTE: NEVER use the func_bobbing in q3tourney6 + bs->tfl &= ~TFL_FUNCBOB; + //if the bot is below the bounding box + if (bs->origin[0] > mins[0] && bs->origin[0] < maxs[0]) { + if (bs->origin[1] > mins[1] && bs->origin[1] < maxs[1]) { + if (bs->origin[2] < mins[2]) { + return; + } + } + } + shootbutton = qfalse; + //if an enemy is below this bounding box then shoot the button + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + + if (i == bs->client) continue; + // + BotEntityInfo(i, &entinfo); + // + if (!entinfo.valid) continue; + //if the enemy isn't dead and the enemy isn't the bot self + if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; + // + if (entinfo.origin[0] > mins[0] && entinfo.origin[0] < maxs[0]) { + if (entinfo.origin[1] > mins[1] && entinfo.origin[1] < maxs[1]) { + if (entinfo.origin[2] < mins[2]) { + //if there's a team mate below the crusher + if (BotSameTeam(bs, i)) { + shootbutton = qfalse; + break; + } + else { + shootbutton = qtrue; + } + } + } + } + } + if (shootbutton) { + bs->flags |= BFL_IDEALVIEWSET; + VectorSubtract(buttonorg, bs->eye, dir); + vectoangles(dir, bs->ideal_viewangles); + aim_accuracy = 1; + bs->ideal_viewangles[PITCH] += 8 * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]); + bs->ideal_viewangles[YAW] += 8 * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]); + // + if (InFieldOfVision(bs->viewangles, 20, bs->ideal_viewangles)) + {// check in here to either call the alt_fire or not!! also, adjust the bots weapon... hackery? + if (bs->weaponnum > WP_NUM_WEAPONS) // it's an alt_fire! + { + bs->weaponnum -= WP_NUM_WEAPONS; + trap_EA_Alt_Attack(bs->client); + } + else + { + trap_EA_Attack(bs->client); + } + } + } + } +} + +/* +================== +BotEntityToActivate +================== +*/ +//#define OBSTACLEDEBUG + +int BotEntityToActivate(int entitynum) { + int i, ent, cur_entities[10]; + char model[MAX_INFO_STRING], tmpmodel[128]; + char target[128], classname[128]; + float health; + char targetname[10][128]; + aas_entityinfo_t entinfo; + + BotEntityInfo(entitynum, &entinfo); + Com_sprintf(model, sizeof( model ), "*%d", entinfo.modelindex); + for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { + if (!trap_AAS_ValueForBSPEpairKey(ent, "model", tmpmodel, sizeof(tmpmodel))) continue; + if (!strcmp(model, tmpmodel)) break; + } + if (!ent) { + BotAI_Print(PRT_ERROR, "BotEntityToActivate: no entity found with model %s\n", model); + return 0; + } + trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname)); + if (!classname) { + BotAI_Print(PRT_ERROR, "BotEntityToActivate: entity with model %s has no classname\n", model); + return 0; + } + //if it is a door + if (!strcmp(classname, "func_door")) { + if (trap_AAS_FloatForBSPEpairKey(ent, "health", &health)) { + //if health the door must be shot to open + if (health) return ent; + } + } + //get the targetname so we can find an entity with a matching target + if (!trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[0], sizeof(targetname[0]))) { +#ifdef OBSTACLEDEBUG + BotAI_Print(PRT_ERROR, "BotEntityToActivate: entity with model \"%s\" has no targetname\n", model); +#endif //OBSTACLEDEBUG + return 0; + } + //only allows single activation chains, tree-like activation can be added back in + cur_entities[0] = trap_AAS_NextBSPEntity(0); + for (i = 0; i >= 0 && i < 10;) { + for (ent = cur_entities[i]; ent; ent = trap_AAS_NextBSPEntity(ent)) { + if (!trap_AAS_ValueForBSPEpairKey(ent, "target", target, sizeof(target))) continue; + if (!strcmp(targetname[i], target)) { + cur_entities[i] = trap_AAS_NextBSPEntity(ent); + break; + } + } + if (!ent) { + BotAI_Print(PRT_ERROR, "BotEntityToActivate: no entity with target \"%s\"\n", targetname[i]); + i--; + continue; + } + if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname))) { + BotAI_Print(PRT_ERROR, "BotEntityToActivate: entity with target \"%s\" has no classname\n", targetname[i]); + continue; + } + if (!strcmp(classname, "func_button")) { + //BSP button model + return ent; + } + else if (!strcmp(classname, "trigger_multiple")) { + //invisible trigger multiple box + return ent; + } + else { + i--; + } + } + BotAI_Print(PRT_ERROR, "BotEntityToActivate: unknown activator with classname \"%s\"\n", classname); + return 0; +} + +/* +================== +BotSetMovedir +================== +*/ +vec3_t VEC_UP = {0, -1, 0}; +vec3_t MOVEDIR_UP = {0, 0, 1}; +vec3_t VEC_DOWN = {0, -2, 0}; +vec3_t MOVEDIR_DOWN = {0, 0, -1}; + +void BotSetMovedir(vec3_t angles, vec3_t movedir) { + if (VectorCompare(angles, VEC_UP)) { + VectorCopy(MOVEDIR_UP, movedir); + } + else if (VectorCompare(angles, VEC_DOWN)) { + VectorCopy(MOVEDIR_DOWN, movedir); + } + else { + AngleVectors(angles, movedir, NULL, NULL); + } +} + +/* +================== +BotModelMinsMaxs + +this is ugly +================== +*/ +void BotModelMinsMaxs(int modelindex, int eType, vec3_t mins, vec3_t maxs) { + gentity_t *ent; + int i; + + ent = &g_entities[0]; + for (i = 0; i < level.num_entities; i++, ent++) { + if ( !ent->inuse ) { + continue; + } + if ( ent->s.eType != eType) { + continue; + } + if (ent->s.modelindex == modelindex) { + VectorAdd(ent->r.currentOrigin, ent->r.mins, mins); + VectorAdd(ent->r.currentOrigin, ent->r.maxs, maxs); + return; + } + } + VectorClear(mins); + VectorClear(maxs); +} + +/* +================== +BotAIBlocked + +very basic handling of bots being blocked by other entities +check what kind of entity is blocking the bot and try to activate +it otherwise try to walk around the entity +before the bot ends in this part of the AI it should predict which doors to open, +which buttons to activate etc. +================== +*/ +void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate) { + int movetype, ent, i, areas[10], numareas, modelindex; + char classname[128], model[128]; + float lip, dist, health, angle; + vec3_t hordir, size, start, end, mins, maxs, sideward, angles; + vec3_t movedir, origin, goalorigin, bboxmins, bboxmaxs; + vec3_t up = {0, 0, 1}, extramins = {1, 1, 1}, extramaxs = {-1, -1, -1}; + aas_entityinfo_t entinfo; + //bsp_trace_t bsptrace; +#ifdef OBSTACLEDEBUG + char netname[MAX_NETNAME]; + char buf[128]; +#endif + + if (!moveresult->blocked) { + bs->notblocked_time = trap_AAS_Time(); + return; + } + // + BotEntityInfo(moveresult->blockentity, &entinfo); +#ifdef OBSTACLEDEBUG + ClientName(bs->client, netname, sizeof(netname)); + BotAI_Print(PRT_MESSAGE, "%s: I'm blocked by model %d\n", netname, entinfo.modelindex); +#endif + //if blocked by a bsp model and the bot wants to activate it if possible + if (entinfo.modelindex > 0 && entinfo.modelindex <= max_bspmodelindex && activate) { + //find the bsp entity which should be activated in order to remove + //the blocking entity + ent = BotEntityToActivate(entinfo.number); + if (!ent) { + strcpy(classname, ""); +#ifdef OBSTACLEDEBUG + BotAI_Print(PRT_MESSAGE, "%s: can't find activator for blocking entity\n", ClientName(bs->client, netname, sizeof(netname))); +#endif + } + else { + trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname)); +#ifdef OBSTACLEDEBUG + ClientName(bs->client, netname, sizeof(netname)); + BotAI_Print(PRT_MESSAGE, "%s: I should activate %s\n", netname, classname); +#endif + } + if (!strcmp(classname, "func_button")) { + //create a bot goal towards the button + trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model)); + modelindex = atoi(model+1); + //if the model is not loaded + if (!modelindex) return; + VectorClear(angles); + BotModelMinsMaxs(modelindex, ET_MOVER, mins, maxs); + //get the lip of the button + trap_AAS_FloatForBSPEpairKey(ent, "lip", &lip); + if (!lip) lip = 4; + //get the move direction from the angle + trap_AAS_FloatForBSPEpairKey(ent, "angle", &angle); + VectorSet(angles, 0, angle, 0); + BotSetMovedir(angles, movedir); + //button size + VectorSubtract(maxs, mins, size); + //button origin + VectorAdd(mins, maxs, origin); + VectorScale(origin, 0.5, origin); + //touch distance of the button + dist = fabs(movedir[0]) * size[0] + fabs(movedir[1]) * size[1] + fabs(movedir[2]) * size[2]; + dist *= 0.5; + // + trap_AAS_FloatForBSPEpairKey(ent, "health", &health); + //if the button is shootable + if (health) { + //FIXME: walk to a point where the button is visible and shoot at the button + //calculate the goal origin + VectorMA(origin, -dist, movedir, goalorigin); + // + VectorSubtract(goalorigin, bs->origin, movedir); + vectoangles(movedir, moveresult->ideal_viewangles); + moveresult->flags |= MOVERESULT_MOVEMENTVIEW; + moveresult->flags |= MOVERESULT_MOVEMENTWEAPON; + //select the machinegun and shoot + trap_EA_SelectWeapon(bs->client, WEAPONINDEX_PHASER); + if (bs->cur_ps.weapon == WEAPONINDEX_PHASER) { + trap_EA_Attack(bs->client); + } + return; + } + else { + //add bounding box size to the dist + trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs); + for (i = 0; i < 3; i++) { + if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]); + else dist += fabs(movedir[i]) * fabs(bboxmins[i]); + } + //calculate the goal origin + VectorMA(origin, -dist, movedir, goalorigin); + // + VectorCopy(goalorigin, start); + start[2] += 24; + VectorCopy(start, end); + end[2] -= 100; + numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); + // + for (i = 0; i < numareas; i++) { + if (trap_AAS_AreaReachability(areas[i])) { + break; + } + } + if (i < numareas) { + // +#ifdef OBSTACLEDEBUG + if (bs->activatemessage_time < trap_AAS_Time()) { + Com_sprintf(buf, sizeof(buf), "I have to activate a button at %1.1f %1.1f %1.1f in area %d\n", + goalorigin[0], goalorigin[1], goalorigin[2], areas[i]); + trap_EA_Say(bs->client, buf); + bs->activatemessage_time = trap_AAS_Time() + 5; + } +#endif //OBSTACLEDEBUG + // + VectorCopy(origin, bs->activategoal.origin); + bs->activategoal.areanum = areas[i]; + VectorSubtract(mins, origin, bs->activategoal.mins); + VectorSubtract(maxs, origin, bs->activategoal.maxs); + // + for (i = 0; i < 3; i++) + { + if (movedir[i] < 0) bs->activategoal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]); + else bs->activategoal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]); + } //end for + // + bs->activategoal.entitynum = entinfo.number; + bs->activategoal.number = 0; + bs->activategoal.flags = 0; + bs->activate_time = trap_AAS_Time() + 10; + AIEnter_Seek_ActivateEntity(bs); + return; + } + else { +#ifdef OBSTACLEDEBUG + if (!numareas) BotAI_Print(PRT_MESSAGE, "button not in an area\n"); + else BotAI_Print(PRT_MESSAGE, "button area has no reachabilities\n"); +#endif //OBSTACLEDEBUG + if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0; + else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0; + } + } + } + else if (!strcmp(classname, "func_door")) { + //shoot at the shootable door + trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model)); + modelindex = atoi(model+1); + //if the model is not loaded + if (!modelindex) return; + VectorClear(angles); + BotModelMinsMaxs(modelindex, ET_MOVER, mins, maxs); + //door origin + VectorAdd(mins, maxs, origin); + VectorScale(origin, 0.5, origin); + // + VectorSubtract(origin, bs->eye, movedir); + vectoangles(movedir, moveresult->ideal_viewangles); + moveresult->flags |= MOVERESULT_MOVEMENTVIEW; + moveresult->flags |= MOVERESULT_MOVEMENTWEAPON; + //select the machinegun and shoot + trap_EA_SelectWeapon(bs->client, WEAPONINDEX_PHASER); + if (bs->cur_ps.weapon == WEAPONINDEX_PHASER) { + trap_EA_Attack(bs->client); + } + return; + } + } + //just some basic dynamic obstacle avoidance code + hordir[0] = moveresult->movedir[0]; + hordir[1] = moveresult->movedir[1]; + hordir[2] = 0; + //if no direction just take a random direction + if (VectorNormalize(hordir) < 0.1) { + VectorSet(angles, 0, 360 * random(), 0); + AngleVectors(angles, hordir, NULL, NULL); + } + // + //if (moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) movetype = MOVE_JUMP; + //else + movetype = MOVE_WALK; + //if there's an obstacle at the bot's feet and head then + //the bot might be able to crouch through + VectorCopy(bs->origin, start); + start[2] += 18; + VectorMA(start, 5, hordir, end); + VectorSet(mins, -16, -16, -24); + VectorSet(maxs, 16, 16, 4); + // + //bsptrace = AAS_Trace(start, mins, maxs, end, bs->entitynum, MASK_PLAYERSOLID); + //if (bsptrace.fraction >= 1) movetype = MOVE_CROUCH; + //get the sideward vector + CrossProduct(hordir, up, sideward); + // + if (bs->flags & BFL_AVOIDRIGHT) VectorNegate(sideward, sideward); + //try to crouch straight forward? + if (movetype != MOVE_CROUCH || !trap_BotMoveInDirection(bs->ms, hordir, 400, movetype)) { + //perform the movement + if (!trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) { + //flip the avoid direction flag + bs->flags ^= BFL_AVOIDRIGHT; + //flip the direction + VectorNegate(sideward, sideward); + //move in the other direction + trap_BotMoveInDirection(bs->ms, sideward, 400, movetype); + } + } + // + if (bs->notblocked_time < trap_AAS_Time() - 0.4) { + //just reset goals and hope the bot will go into another direction + //is this still needed?? + if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0; + else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0; + } +} + +/* +================== +BotCheckConsoleMessages +================== +*/ +void BotCheckConsoleMessages(bot_state_t *bs) { + char botname[MAX_NETNAME], message[MAX_MESSAGE_SIZE], netname[MAX_NETNAME], *ptr; + float chat_reply; + int context, handle; + bot_consolemessage_t m; + bot_match_t match; + + //the name of this bot + ClientName(bs->client, botname, sizeof(botname)); + // + while((handle = trap_BotNextConsoleMessage(bs->cs, &m)) != 0) { + //if the chat state is flooded with messages the bot will read them quickly + if (trap_BotNumConsoleMessages(bs->cs) < 10) { + //if it is a chat message the bot needs some time to read it + if (m.type == CMS_CHAT && m.time > trap_AAS_Time() - (1 + random())) break; + } + // + ptr = m.message; + //if it is a chat message then don't unify white spaces and don't + //replace synonyms in the netname + if (m.type == CMS_CHAT) { + // + if (trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) { + ptr = m.message + match.variables[MESSAGE].offset; + } + } + //unify the white spaces in the message + trap_UnifyWhiteSpaces(ptr); + //replace synonyms in the right context + context = CONTEXT_NORMAL|CONTEXT_NEARBYITEM|CONTEXT_NAMES; + if (BotCTFTeam(bs) == CTF_TEAM_RED) context |= CONTEXT_CTFREDTEAM; + else context |= CONTEXT_CTFBLUETEAM; + trap_BotReplaceSynonyms(ptr, context); + //if there's no match + if (!BotMatchMessage(bs, m.message)) { + //if it is a chat message + if (m.type == CMS_CHAT && !bot_nochat.integer) { + // + if (!trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) { + trap_BotRemoveConsoleMessage(bs->cs, handle); + continue; + } + //don't use eliza chats with team messages + if (match.subtype & ST_TEAM) { + trap_BotRemoveConsoleMessage(bs->cs, handle); + continue; + } + // + trap_BotMatchVariable(&match, NETNAME, netname, sizeof(netname)); + trap_BotMatchVariable(&match, MESSAGE, message, sizeof(message)); + //if this is a message from the bot self + if (!Q_stricmp(netname, botname)) { + trap_BotRemoveConsoleMessage(bs->cs, handle); + continue; + } + //unify the message + trap_UnifyWhiteSpaces(message); + // + trap_Cvar_Update(&bot_testrchat); + if (bot_testrchat.integer) { + // + trap_BotLibVarSet("bot_testrchat", "1"); + //if bot replies with a chat message + if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY, + NULL, NULL, + NULL, NULL, + NULL, NULL, + botname, netname)) { + BotAI_Print(PRT_MESSAGE, "------------------------\n"); + } + else { + BotAI_Print(PRT_MESSAGE, "**** no valid reply ****\n"); + } + } + //if at a valid chat position and not chatting already + else if (bs->ainode != AINode_Stand && BotValidChatPosition(bs)) { + chat_reply = 0; + if (random() < 1.5 / (NumBots()+1) && random() < chat_reply) { + //if bot replies with a chat message + if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY, + NULL, NULL, + NULL, NULL, + NULL, NULL, + botname, netname)) { + //remove the console message + trap_BotRemoveConsoleMessage(bs->cs, handle); + bs->stand_time = trap_AAS_Time() + BotChatTime(bs); + AIEnter_Stand(bs); + //EA_Say(bs->client, bs->cs.chatmessage); + break; + } + } + } + } + } + //remove the console message + trap_BotRemoveConsoleMessage(bs->cs, handle); + } +} + +/* +================== +BotCheckEvents +================== +*/ +void BotCheckEvents(bot_state_t *bs, entityState_t *state) { + int event; + char buf[128]; + // + //NOTE: this sucks, we're accessing the gentity_t directly + //but there's no other fast way to do it right now + if (bs->entityeventTime[state->number] == g_entities[state->number].eventTime) { + return; + } + bs->entityeventTime[state->number] = g_entities[state->number].eventTime; + //if it's an event only entity + if (state->eType > ET_EVENTS) { + event = (state->eType - ET_EVENTS) & ~EV_EVENT_BITS; + } + else { + event = state->event & ~EV_EVENT_BITS; + } + // + switch(event) { + //client obituary event + case EV_OBITUARY: + { + int target, attacker, mod; + + target = state->otherEntityNum; + attacker = state->otherEntityNum2; + mod = state->eventParm; + // + if (target == bs->client) { + bs->botdeathtype = mod; + bs->lastkilledby = attacker; + // + if (target == attacker) bs->botsuicide = qtrue; + else bs->botsuicide = qfalse; + // + bs->num_deaths++; + } + //else if this client was killed by the bot + else if (attacker == bs->client) { + bs->enemydeathtype = mod; + bs->lastkilledplayer = target; + bs->killedenemy_time = trap_AAS_Time(); + // + bs->num_kills++; + } + else if (attacker == bs->enemy && target == attacker) { + bs->enemysuicide = qtrue; + } + break; + } + + case EV_GLOBAL_SOUND: + { + if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) { + BotAI_Print(PRT_ERROR, "EV_GLOBAL_SOUND: eventParm (%d) out of range\n", state->eventParm); + break; + } + else { + trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf)); + if (!strcmp(buf, "sound/items/poweruprespawn.wav")) { + //powerup respawned... go get it + BotGoForPowerups(bs); + } + } + break; + } + + case EV_TEAM_SOUND: + { + if (state->eventParm < 0 || state->eventParm > MAX_TEAM_SOUNDS) { + BotAI_Print(PRT_ERROR, "EV_TEAM_SOUND: eventParm (%d) out of range\n", state->eventParm); + break; + } + + if (state->eventParm == RETURN_FLAG_SOUND) + { + if (state->otherEntityNum == TEAM_RED) + { + //red flag is returned + bs->redflagstatus = 0; + bs->flagstatuschanged = qtrue; + } + else + { + //blue flag is returned + bs->blueflagstatus = 0; + bs->flagstatuschanged = qtrue; + } + } + break; + } + + + + case EV_PLAYER_TELEPORT_IN: + { + VectorCopy(state->origin, lastteleport_origin); + lastteleport_time = trap_AAS_Time(); + break; + } + case EV_GENERAL_SOUND: + { + //if this sound is played on the bot + if (state->number == bs->client) { + if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) { + BotAI_Print(PRT_ERROR, "EV_GENERAL_SOUND: eventParm (%d) out of range\n", state->eventParm); + break; + } + //check out the sound + trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf)); + //if falling into a death pit + if (!strcmp(buf, "*falling1.wav")) { + //if the bot has a personal teleporter + if (bs->inventory[INVENTORY_TRANSPORTER] > 0) { + //use the holdable item + trap_EA_Use(bs->client); + } + } + } + break; + } + } +} + +/* +================== +BotCheckSnapshot +================== +*/ +void BotCheckSnapshot(bot_state_t *bs) { + int ent; + entityState_t state; + + // + ent = 0; + while( ( ent = BotAI_GetSnapshotEntity( bs->client, ent, &state ) ) != -1 ) { + //check the entity state for events + BotCheckEvents(bs, &state); + } + //check the player state for events + BotAI_GetEntityState(bs->client, &state); + //copy the player state events to the entity state + state.event = bs->cur_ps.externalEvent; + state.eventParm = bs->cur_ps.externalEventParm; + // + BotCheckEvents(bs, &state); +} + +/* +================== +BotCheckAir +================== +*/ +void BotCheckAir(bot_state_t *bs) { + if (bs->inventory[INVENTORY_ENVIRONMENTSUIT] <= 0) { + if (trap_AAS_PointContents(bs->eye) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) { + return; + } + } + bs->lastair_time = trap_AAS_Time(); +} + +/* +================== +BotDeathmatchAI +================== +*/ +void BotDeathmatchAI(bot_state_t *bs, float thinktime) { + char gender[144], name[144], buf[144]; + char userinfo[MAX_INFO_STRING]; + int i; + + //if the bot has just been setup + if (bs->setupcount > 0) { + bs->setupcount--; + if (bs->setupcount > 0) return; + //get the gender characteristic + trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, sizeof(gender)); + //set the bot gender + trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo)); + Info_SetValueForKey(userinfo, "sex", gender); + trap_SetUserinfo(bs->client, userinfo); + //set the team + if ( g_gametype.integer != GT_TOURNAMENT ) { + Com_sprintf(buf, sizeof(buf), "team %s", bs->settings.team); + trap_EA_Command(bs->client, buf); + } + if ( g_pModSpecialties.integer ) { + Com_sprintf(buf, sizeof(buf), "class %s", bs->settings.pclass); + trap_EA_Command(bs->client, buf); + } + //set the chat gender + if (gender[0] == 'm') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE); + else if (gender[0] == 'f') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE); + else trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS); + //set the chat name + ClientName(bs->client, name, sizeof(name)); + trap_BotSetChatName(bs->cs, name); + // + bs->lastframe_health = bs->inventory[INVENTORY_HEALTH]; + bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS]; + // + bs->setupcount = 0; + } + //no ideal view set + bs->flags &= ~BFL_IDEALVIEWSET; + //set the teleport time + BotSetTeleportTime(bs); + //update some inventory values + BotUpdateInventory(bs); + //check the console messages + BotCheckConsoleMessages(bs); + //check out the snapshot + BotCheckSnapshot(bs); + //check for air + BotCheckAir(bs); + //if not in the intermission and not in observer mode + if (!BotIntermission(bs) && !BotIsObserver(bs)) { + //do team AI + BotTeamAI(bs); + } + //if the bot has no ai node + if (!bs->ainode) { + AIEnter_Seek_LTG(bs); + } + //if the bot entered the game less than 8 seconds ago + if (!bs->entergamechat && bs->entergame_time > trap_AAS_Time() - 8) { + if (BotChat_EnterGame(bs)) { + bs->stand_time = trap_AAS_Time() + BotChatTime(bs); + AIEnter_Stand(bs); + } + bs->entergamechat = qtrue; + } + //reset the node switches from the previous frame + BotResetNodeSwitches(); + //execute AI nodes + for (i = 0; i < MAX_NODESWITCHES; i++) { + if (bs->ainode(bs)) break; + } + //if the bot removed itself :) + if (!bs->inuse) return; + //if the bot executed too many AI nodes + if (i >= MAX_NODESWITCHES) { + trap_BotDumpGoalStack(bs->gs); + trap_BotDumpAvoidGoals(bs->gs); + BotDumpNodeSwitches(bs); + ClientName(bs->client, name, sizeof(name)); + BotAI_Print(PRT_ERROR, "%s at %1.1f switched more than %d AI nodes\n", name, trap_AAS_Time(), MAX_NODESWITCHES); + } + // + bs->lastframe_health = bs->inventory[INVENTORY_HEALTH]; + bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS]; +} + +/* +================== +BotSetupDeathmatchAI +================== +*/ +void BotSetupDeathmatchAI(void) { + int ent, modelnum; + char model[128]; + + gametype = trap_Cvar_VariableIntegerValue("g_gametype"); + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + trap_Cvar_Register(&bot_rocketjump, "bot_rocketjump", "1", 0); + trap_Cvar_Register(&bot_grapple, "bot_grapple", "0", 0); + trap_Cvar_Register(&bot_fastchat, "bot_fastchat", "0", 0); + trap_Cvar_Register(&bot_nochat, "bot_nochat", "0", 0); + trap_Cvar_Register(&bot_testrchat, "bot_testrchat", "0", 0); + trap_Cvar_Register(&bot_challenge, "bot_challenge", "0", 0); + // + if (gametype == GT_CTF) { + if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0) + BotAI_Print(PRT_WARNING, "CTF without Red Flag\n"); + if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0) + BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n"); + } + + max_bspmodelindex = 0; + for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { + if (!trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model))) continue; + if (model[0] == '*') { + modelnum = atoi(model+1); + if (modelnum > max_bspmodelindex) + max_bspmodelindex = modelnum; + } + } + //initialize the waypoint heap + BotInitWaypoints(); +} + +/* +================== +BotShutdownDeathmatchAI +================== +*/ +void BotShutdownDeathmatchAI(void) { +} + diff --git a/game/ai_dmq3.c.orig b/game/ai_dmq3.c.orig new file mode 100644 index 0000000..3bc9787 --- /dev/null +++ b/game/ai_dmq3.c.orig @@ -0,0 +1,2857 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +/***************************************************************************** + * name: ai_dmq3.c + * + * desc: Quake3 bot AI + * + * $Archive: /StarTrek/Code-DM/game/ai_dmq3.c $ + * $Author: Mgummelt $ + * $Revision: 33 $ + * $Modtime: 4/04/01 5:01p $ + * $Date: 4/04/01 5:17p $ + * + *****************************************************************************/ + + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +#define IDEAL_ATTACKDIST 140 +#define WEAPONINDEX_PHASER 2 + +#define MAX_WAYPOINTS 128 +// +bot_waypoint_t botai_waypoints[MAX_WAYPOINTS]; +bot_waypoint_t *botai_freewaypoints; + +//NOTE: not using a cvars which can be updated because the game should be reloaded anyway +int gametype; //game type +int maxclients; //maximum number of clients + +vmCvar_t bot_grapple; +vmCvar_t bot_rocketjump; +vmCvar_t bot_fastchat; +vmCvar_t bot_nochat; +vmCvar_t bot_testrchat; +vmCvar_t bot_challenge; + +vec3_t lastteleport_origin; //last teleport event origin +float lastteleport_time; //last teleport event time +int max_bspmodelindex; //maximum BSP model index + +//CTF flag goals +bot_goal_t ctf_redflag; +bot_goal_t ctf_blueflag; + +#ifdef CTF +/* +================== +BotCTFCarryingFlag +================== +*/ +int BotCTFCarryingFlag(bot_state_t *bs) { + if (gametype != GT_CTF) return CTF_FLAG_NONE; + + if (bs->inventory[INVENTORY_REDFLAG] > 0) return CTF_FLAG_RED; + else if (bs->inventory[INVENTORY_BLUEFLAG] > 0) return CTF_FLAG_BLUE; + return CTF_FLAG_NONE; +} + +/* +================== +BotCTFTeam +================== +*/ +int BotCTFTeam(bot_state_t *bs) { + char info[1024]; + + if (gametype != GT_CTF) return CTF_TEAM_NONE; + if (bs->client < 0 || bs->client >= MAX_CLIENTS) { + //BotAI_Print(PRT_ERROR, "BotCTFTeam: client out of range\n"); + return qfalse; + } + trap_GetConfigstring(CS_PLAYERS+bs->client, info, sizeof(info)); + // + if (atoi(Info_ValueForKey(info, "t")) == TEAM_RED) return CTF_TEAM_RED; + else if (atoi(Info_ValueForKey(info, "t")) == TEAM_BLUE) return CTF_TEAM_BLUE; + return CTF_TEAM_NONE; +} + +/* +================== +BotCTFRetreatGoals +================== +*/ +void BotCTFRetreatGoals(bot_state_t *bs) { + //when carrying a flag in ctf the bot should rush to the base + if (BotCTFCarryingFlag(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = trap_AAS_Time() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + } + } +} + +/* +================== +EntityIsDead +================== +*/ +qboolean EntityIsDead(aas_entityinfo_t *entinfo) { + playerState_t ps; + + if (entinfo->number >= 0 && entinfo->number < MAX_CLIENTS) { + //retrieve the current client state + BotAI_GetClientState( entinfo->number, &ps ); + if (ps.pm_type != PM_NORMAL) return qtrue; + } + return qfalse; +} + +/* +================== +EntityIsInvisible +================== +*/ +qboolean EntityIsInvisible(aas_entityinfo_t *entinfo) { + if (entinfo->powerups & (1 << PW_GHOST)) + { // 50% chance of being visible? + if (((unsigned int)(level.time)/1024)&0x01) // Every second or so, the bot will see the player, so he doesn't jitter. + { + return qtrue; + } + else + { + return qfalse; + } + } + else if (entinfo->powerups & (1 << PW_INVIS)) + { + return qtrue; + } + else if ( entinfo->flags & EF_NODRAW ) + { + return qtrue; + } + return qfalse; +} + +/* +================== +EntityCarriesFlag +================== +*/ +qboolean EntityCarriesFlag(aas_entityinfo_t *entinfo) { + if ( entinfo->powerups & ( 1 << PW_REDFLAG ) ) return qtrue; + if ( entinfo->powerups & ( 1 << PW_BLUEFLAG ) ) return qtrue; + return qfalse; +} + +/* +================== +EntityIsShooting +================== +*/ +qboolean EntityIsShooting(aas_entityinfo_t *entinfo) { + if (entinfo->flags & EF_FIRING) { + return qtrue; + } + return qfalse; +} + +/* +================== +EntityIsChatting +================== +*/ +qboolean EntityIsChatting(aas_entityinfo_t *entinfo) { + if (entinfo->flags & EF_TALK) { + return qtrue; + } + return qfalse; +} + +/* +================== +EntityHasQuad +================== +*/ +qboolean EntityHasQuad(aas_entityinfo_t *entinfo) { + if (entinfo->powerups & (1 << PW_QUAD)) { + return qtrue; + } + return qfalse; +} + +/* +================== +BotCTFSeekGoals +================== +*/ +void BotCTFSeekGoals(bot_state_t *bs) { + float rnd; + int flagstatus, c; + + //when carrying a flag in ctf the bot should rush to the base + if (BotCTFCarryingFlag(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = trap_AAS_Time() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + } + else if (bs->rushbaseaway_time > trap_AAS_Time()) { + if (BotCTFTeam(bs) == CTF_TEAM_RED) flagstatus = bs->redflagstatus; + else flagstatus = bs->blueflagstatus; + //if the flag is back + if (flagstatus == 0) { + bs->rushbaseaway_time = 0; + } + } + return; + } + // + if (BotCTFTeam(bs) == CTF_TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus; + else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus; + //if the enemy flag is not at it's base + if (flagstatus == 1) { + //if Not defending the base + if (!(bs->ltgtype == LTG_DEFENDKEYAREA && + (bs->teamgoal.number == ctf_redflag.number || + bs->teamgoal.number == ctf_blueflag.number))) { + //if not already accompanying someone + if (bs->ltgtype != LTG_TEAMACCOMPANY) { + //if there is avisible team mate flag carrier + c = BotTeamFlagCarrierVisible(bs); + if (c >= 0) { + //follow the flag carrier + //the team mate + bs->teammate = c; + //last time the team mate was visible + bs->teammatevisible_time = trap_AAS_Time(); + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //get the team goal time + bs->teamgoal_time = trap_AAS_Time() + TEAM_ACCOMPANY_TIME; + bs->ltgtype = LTG_TEAMACCOMPANY; + bs->formation_dist = 3.5 * 32; //3.5 meter + bs->arrive_time = 0; + return; + } + } + } + } + //if the base flag is stolen + else if (flagstatus == 2) { + //if not already going for the enemy flag + if (bs->ltgtype != LTG_GETFLAG) { + //if there's no bot team leader + if (!BotTeamLeader(bs)) { + //go for the enemy flag + bs->ltgtype = LTG_GETFLAG; + //no team message + bs->teammessage_time = 1; + //set the time the bot will stop getting the flag + bs->teamgoal_time = trap_AAS_Time() + CTF_GETFLAG_TIME; + return; + } + } + } + //if both flags not at their bases + else if (flagstatus == 3) { + // + if (bs->ltgtype != LTG_GETFLAG && + bs->ltgtype != LTG_TEAMACCOMPANY) { + //if there is avisible team mate flag carrier + c = BotTeamFlagCarrierVisible(bs); + if (c >= 0) { + //follow the flag carrier + return; + } + else { + //otherwise attack the enemy base + } + return; + } + } + //if the bot is roaming + if (bs->ctfroam_time > trap_AAS_Time()) return; + //if already a CTF or team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_RETURNFLAG || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL) { + return; + } + //if the bot has anough aggression to decide what to do + if (BotAggression(bs) < 50) return; + //set the time to send a message to the team mates + bs->teammessage_time = trap_AAS_Time() + 2 * random(); + //get the flag or defend the base + rnd = random(); + if (rnd < 0.33 && ctf_redflag.areanum && ctf_blueflag.areanum) { + bs->ltgtype = LTG_GETFLAG; + //set the time the bot will stop getting the flag + bs->teamgoal_time = trap_AAS_Time() + CTF_GETFLAG_TIME; + } + else if (rnd < 0.66 && ctf_redflag.areanum && ctf_blueflag.areanum) { + // + if (BotCTFTeam(bs) == CTF_TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stops defending the base + bs->teamgoal_time = trap_AAS_Time() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + } + else { + bs->ltgtype = 0; + //set the time the bot will stop roaming + bs->ctfroam_time = trap_AAS_Time() + CTF_ROAM_TIME; + } +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +#endif //CTF + +/* +================== +BotPointAreaNum +================== +*/ +int BotPointAreaNum(vec3_t origin) { + int areanum, numareas, areas[10]; + vec3_t end; + + areanum = trap_AAS_PointAreaNum(origin); + if (areanum) return areanum; + VectorCopy(origin, end); + end[2] += 10; + numareas = trap_AAS_TraceAreas(origin, end, areas, NULL, 10); + if (numareas > 0) return areas[0]; + return 0; +} + +/* +================== +ClientName +================== +*/ +char *ClientName(int client, char *name, int size) { + char buf[MAX_INFO_STRING]; + + if (client < 0 || client >= MAX_CLIENTS) { + BotAI_Print(PRT_ERROR, "ClientName: client out of range\n"); + return "[client out of range]"; + } + trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf)); + strncpy(name, Info_ValueForKey(buf, "n"), size-1); + name[size-1] = '\0'; + Q_CleanStr( name ); + return name; +} + +/* +================== +ClientSkin +================== +*/ +char *ClientSkin(int client, char *skin, int size) { + char buf[MAX_INFO_STRING]; + + if (client < 0 || client >= MAX_CLIENTS) { + BotAI_Print(PRT_ERROR, "ClientSkin: client out of range\n"); + return "[client out of range]"; + } + trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf)); + strncpy(skin, Info_ValueForKey(buf, "model"), size-1); + skin[size-1] = '\0'; + return skin; +} + +/* +================== +ClientFromName +================== +*/ +int ClientFromName(char *name) { + int i; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + Q_CleanStr( buf ); + if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i; + } + return -1; +} + +/* +================== +stristr +================== +*/ +char *stristr(char *str, char *charset) { + int i; + + while(*str) { + for (i = 0; charset[i] && str[i]; i++) { + if (toupper(charset[i]) != toupper(str[i])) break; + } + if (!charset[i]) return str; + str++; + } + return NULL; +} + +/* +================== +EasyClientName +================== +*/ +char *EasyClientName(int client, char *buf, int size) { + int i; + char *str1, *str2, *ptr, c; + char name[128]; + + strcpy(name, ClientName(client, name, sizeof(name))); + for (i = 0; name[i]; i++) name[i] &= 127; + //remove all spaces + for (ptr = strstr(name, " "); ptr; ptr = strstr(name, " ")) { + memmove(ptr, ptr+1, strlen(ptr+1)+1); + } + //check for [x] and ]x[ clan names + str1 = strstr(name, "["); + str2 = strstr(name, "]"); + if (str1 && str2) { + if (str2 > str1) memmove(str1, str2+1, strlen(str2+1)+1); + else memmove(str2, str1+1, strlen(str1+1)+1); + } + //remove Mr prefix + if ((name[0] == 'm' || name[0] == 'M') && + (name[1] == 'r' || name[1] == 'R')) { + memmove(name, name+2, strlen(name+2)+1); + } + //only allow lower case alphabet characters + ptr = name; + while(*ptr) { + c = *ptr; + if ((c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || c == '_') { + ptr++; + } + else if (c >= 'A' && c <= 'Z') { + *ptr += 'a' - 'A'; + ptr++; + } + else { + memmove(ptr, ptr+1, strlen(ptr + 1)+1); + } + } + strncpy(buf, name, size-1); + buf[size-1] = '\0'; + return buf; +} + +qboolean BotUseMeleeWeapon(bot_state_t *bs) { + if ( bs->inventory[ENEMY_HORIZONTAL_DIST] < 64 ) + { + if ( bs->cur_ps.persistant[PERS_CLASS] == PC_BORG || bs->cur_ps.persistant[PERS_CLASS] == PC_MEDIC ) + { + return qtrue; + } + } + return qfalse; +} +/* +================== +BotChooseWeapon + + MCG - FIXME: This should really take into account: + Projectile vs. instant? + gravity on projectile? + Range to enemy vs range of weapon? + Some randomness on the weights? + +================== +*/ +void BotChooseWeapon(bot_state_t *bs) { + int newweaponnum; + + if (bs->cur_ps.weaponstate == WEAPON_RAISING || bs->cur_ps.weaponstate == WEAPON_DROPPING) + { + trap_EA_SelectWeapon(bs->client, bs->weaponnum); + } + else + { + newweaponnum = trap_BotChooseBestFightWeapon(bs->ws, bs->inventory, BotUseMeleeWeapon(bs)); + if (bs->weaponnum != newweaponnum) + { + bs->weaponchange_time = trap_AAS_Time(); + } + bs->weaponnum = newweaponnum; + //BotAI_Print(PRT_MESSAGE, "bs->weaponnum = %d\n", bs->weaponnum); + trap_EA_SelectWeapon(bs->client, bs->weaponnum); + } +} + +/* +================== +BotSetupForMovement +================== +*/ +void BotSetupForMovement(bot_state_t *bs) { + bot_initmove_t initmove; + + memset(&initmove, 0, sizeof(bot_initmove_t)); + VectorCopy(bs->cur_ps.origin, initmove.origin); + VectorCopy(bs->cur_ps.velocity, initmove.velocity); + VectorCopy(bs->cur_ps.origin, initmove.viewoffset); + initmove.viewoffset[2] += bs->cur_ps.viewheight; + initmove.entitynum = bs->entitynum; + initmove.client = bs->client; + initmove.thinktime = bs->thinktime; + //set the onground flag + if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) initmove.or_moveflags |= MFL_ONGROUND; + //set the teleported flag + if ((bs->cur_ps.pm_flags & PMF_TIME_KNOCKBACK) && (bs->cur_ps.pm_time > 0)) { + initmove.or_moveflags |= MFL_TELEPORTED; + } + //set the waterjump flag + if ((bs->cur_ps.pm_flags & PMF_TIME_WATERJUMP) && (bs->cur_ps.pm_time > 0)) { + initmove.or_moveflags |= MFL_WATERJUMP; + } + //set presence type + if (bs->cur_ps.pm_flags & PMF_DUCKED) initmove.presencetype = PRESENCE_CROUCH; + else initmove.presencetype = PRESENCE_NORMAL; + // + if (bs->walker > 0.5) initmove.or_moveflags |= MFL_WALK; + // + VectorCopy(bs->viewangles, initmove.viewangles); + // + trap_BotInitMoveState(bs->ms, &initmove); +} + +/* +================== +BotUpdateInventory +================== +*/ +void BotUpdateInventory(bot_state_t *bs) { + //armor + bs->inventory[INVENTORY_ARMOR] = bs->cur_ps.stats[STAT_ARMOR]; + + //weapons + bs->inventory[INVENTORY_GRENADELAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRENADE_LAUNCHER)) != 0; + bs->inventory[INVENTORY_STASIS] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_DISRUPTOR)) != 0; + bs->inventory[INVENTORY_PHASER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PHASER)) != 0; + bs->inventory[INVENTORY_DREADNOUGHT] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_DERMAL_REGEN)) != 0; + bs->inventory[INVENTORY_IMOD] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_NULL_HAND)) != 0; + bs->inventory[INVENTORY_COMPRESSION] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_COMPRESSION_RIFLE)) != 0; + bs->inventory[INVENTORY_TETRION] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_TR116)) != 0; + bs->inventory[INVENTORY_SCAVENGER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_COFFEE)) != 0; + bs->inventory[INVENTORY_QUANTUM] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_QUANTUM_BURST)) != 0; + + //ammo + bs->inventory[INVENTORY_GRENADES] = bs->cur_ps.ammo[WP_GRENADE_LAUNCHER]; + bs->inventory[INVENTORY_STASISAMMO] = bs->cur_ps.ammo[WP_DISRUPTOR]; + bs->inventory[INVENTORY_PHASERAMMO] = bs->cur_ps.ammo[WP_PHASER]; + bs->inventory[INVENTORY_DREADNOUGHTAMMO] = bs->cur_ps.ammo[WP_DERMAL_REGEN]; + bs->inventory[INVENTORY_IMODAMMO] = bs->cur_ps.ammo[WP_NULL_HAND]; + bs->inventory[INVENTORY_COMPRESSIONAMMO] = bs->cur_ps.ammo[WP_COMPRESSION_RIFLE]; + bs->inventory[INVENTORY_TETRIONAMMO] = bs->cur_ps.ammo[WP_TR116]; + bs->inventory[INVENTORY_SCAVENGERAMMO] = bs->cur_ps.ammo[WP_COFFEE]; + bs->inventory[INVENTORY_QUANTUMAMMO] = bs->cur_ps.ammo[WP_QUANTUM_BURST]; + + //powerups + bs->inventory[INVENTORY_HEALTH] = bs->cur_ps.stats[STAT_HEALTH]; + bs->inventory[INVENTORY_TRANSPORTER] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_TELEPORTER; + bs->inventory[INVENTORY_MEDKIT] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_MEDKIT; + bs->inventory[INVENTORY_QUAD] = bs->cur_ps.powerups[PW_QUAD] != 0; + bs->inventory[INVENTORY_ENVIRONMENTSUIT] = bs->cur_ps.powerups[PW_BOLTON] != 0; + bs->inventory[INVENTORY_HASTE] = bs->cur_ps.powerups[PW_HASTE] != 0; + bs->inventory[INVENTORY_INVISIBILITY] = bs->cur_ps.powerups[PW_INVIS] != 0; + bs->inventory[INVENTORY_REGEN] = bs->cur_ps.powerups[PW_REGEN] != 0; + bs->inventory[INVENTORY_FLIGHT] = bs->cur_ps.powerups[PW_FLIGHT] != 0; + bs->inventory[INVENTORY_REDFLAG] = bs->cur_ps.powerups[PW_REDFLAG] != 0; + bs->inventory[INVENTORY_BLUEFLAG] = bs->cur_ps.powerups[PW_BLUEFLAG] != 0; + bs->inventory[INVENTORY_SEEKER] = bs->cur_ps.powerups[PW_SEEKER] != 0; + bs->inventory[INVENTORY_SHIELD] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_SHIELD; + bs->inventory[INVENTORY_DETPACK] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_DETPACK; + // +} + +/* +================== +BotUpdateBattleInventory +================== +*/ +void BotUpdateBattleInventory(bot_state_t *bs, int enemy) { + vec3_t dir; + aas_entityinfo_t entinfo; + + BotEntityInfo(enemy, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + bs->inventory[ENEMY_HEIGHT] = (int) dir[2]; + dir[2] = 0; + bs->inventory[ENEMY_HORIZONTAL_DIST] = (int) VectorLength(dir); + //FIXME: add num visible enemies and num visible team mates to the inventory +} + +/* +========================= +BotShouldDetonateDetPack +========================= +*/ +#define DETPACK_RADIUS 500 + +qboolean BotShouldDetonateDetPack(bot_state_t *bs) +{ + int botNum = 0, detWeight = 0; + vec3_t packOrg, dir; + float dist; + aas_entityinfo_t botinfo; + + // find the location of the DetPack + gentity_t *detpack = NULL; + char *classname = BG_FindClassnameForHoldable(HI_DETPACK); + + if (!classname) + { + return qfalse; + } + + while ((detpack = G_Find (detpack, FOFS(classname), classname)) != NULL) + { + VectorCopy(detpack->r.currentOrigin, packOrg); + } + + // determine who would be killed in the blast radius + for (botNum = 0; botNum < MAX_CLIENTS; botNum++) + { + BotEntityInfo(botNum, &botinfo); + if (!botinfo.valid) continue; + + //calculate the distance towards the enemy + VectorSubtract(botinfo.origin, packOrg, dir); + dist = VectorLength(dir); + + if (dist < DETPACK_RADIUS) // bot would get caught in blast radius + { + if (BotSameTeam(bs, botNum)) // friendly casualty potential + { + if (botNum == bs->client) // suicide... bad + { + detWeight--; + } + if (EntityCarriesFlag(&botinfo)) // it's my teammate, and he's got the flag! + { + detWeight -= 11; + continue; + } + detWeight--; + } + else + { + if(EntityCarriesFlag(&botinfo)) // mwahaha + { + detWeight += 14; + } + detWeight++; + } + } + } + +// Com_Printf("detWeight %d\n", detWeight); + + if (detWeight > 0) + { + return qtrue; + } + return qfalse; +} + + + +/* +================== +BotBattleUseItems +================== +*/ +void BotBattleUseItems(bot_state_t *bs) { + + + if (bs->inventory[INVENTORY_DETPACK] > 0) + { + // this needs to be in two stages: placement and detonation + if (bs->ltgtype == LTG_DEFENDKEYAREA) + { + if (bs->inventory[INVENTORY_DETPACK_PLACED] == 0) // not placed yet + { + bs->inventory[INVENTORY_DETPACK_PLACED] = 1; + trap_EA_Use(bs->client); // place it + return; + } + } + + if (bs->inventory[INVENTORY_DETPACK_PLACED] == 1) // placed + { + if (BotShouldDetonateDetPack(bs)) // logic + { + bs->inventory[INVENTORY_DETPACK_PLACED] = 0; + trap_EA_Use(bs->client); // BOOM + return; + } + return; + } + return; + } + + if (bs->inventory[INVENTORY_SHIELD] > 0) + { + if (BotWantsToRetreat(bs) && (bs->inventory[INVENTORY_HEALTH] < 50)) + { + trap_EA_Use(bs->client); + } + return; + } + + if (bs->inventory[INVENTORY_TRANSPORTER] > 0) + { + if (!BotCTFCarryingFlag(bs) && (bs->inventory[INVENTORY_HEALTH] < 50)) + { + trap_EA_Use(bs->client); + return; + } + } + + if (bs->inventory[INVENTORY_MEDKIT] > 0) + { + if (bs->inventory[INVENTORY_HEALTH] < 30) + { + trap_EA_Use(bs->client); + } + return; + } +} + +/* +================== +BotSetTeleportTime +================== +*/ +void BotSetTeleportTime(bot_state_t *bs) { + if ((bs->cur_ps.eFlags ^ bs->last_eFlags) & EF_TELEPORT_BIT) { + bs->teleport_time = trap_AAS_Time(); + } + bs->last_eFlags = bs->cur_ps.eFlags; +} + +/* +================== +BotIsDead +================== +*/ +qboolean BotIsDead(bot_state_t *bs) { + return (bs->cur_ps.pm_type == PM_DEAD); +} + +/* +================== +BotIsObserver +================== +*/ +qboolean BotIsObserver(bot_state_t *bs) { + char buf[MAX_INFO_STRING]; + if (bs->cur_ps.pm_type == PM_SPECTATOR) return qtrue; + trap_GetConfigstring(CS_PLAYERS+bs->client, buf, sizeof(buf)); + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) return qtrue; + return qfalse; +} + +/* +================== +BotIntermission +================== +*/ +qboolean BotIntermission(bot_state_t *bs) { + //NOTE: we shouldn't be looking at the game code... + if (level.intermissiontime) return qtrue; + return (bs->cur_ps.pm_type == PM_FREEZE || bs->cur_ps.pm_type == PM_INTERMISSION); +} + +/* +================== +BotInLavaOrSlime +================== +*/ +qboolean BotInLavaOrSlime(bot_state_t *bs) { + vec3_t feet; + + VectorCopy(bs->origin, feet); + feet[2] -= 23; + return (trap_AAS_PointContents(feet) & (CONTENTS_LAVA|CONTENTS_SLIME)); +} + +/* +================== +BotCreateWayPoint +================== +*/ +bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum) { + bot_waypoint_t *wp; + vec3_t waypointmins = {-8, -8, -8}, waypointmaxs = {8, 8, 8}; + + wp = botai_freewaypoints; + if ( !wp ) { + BotAI_Print( PRT_WARNING, "BotCreateWayPoint: Out of waypoints\n" ); + return NULL; + } + botai_freewaypoints = botai_freewaypoints->next; + + Q_strncpyz( wp->name, name, sizeof(wp->name) ); + VectorCopy(origin, wp->goal.origin); + VectorCopy(waypointmins, wp->goal.mins); + VectorCopy(waypointmaxs, wp->goal.maxs); + wp->goal.areanum = areanum; + wp->next = NULL; + wp->prev = NULL; + return wp; +} + +/* +================== +BotFindWayPoint +================== +*/ +bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name) { + bot_waypoint_t *wp; + + for (wp = waypoints; wp; wp = wp->next) { + if (!Q_stricmp(wp->name, name)) return wp; + } + return NULL; +} + +/* +================== +BotFreeWaypoints +================== +*/ +void BotFreeWaypoints(bot_waypoint_t *wp) { + bot_waypoint_t *nextwp; + + for (; wp; wp = nextwp) { + nextwp = wp->next; + wp->next = botai_freewaypoints; + botai_freewaypoints = wp; + } +} + +/* +================== +BotInitWaypoints +================== +*/ +void BotInitWaypoints(void) { + int i; + + botai_freewaypoints = NULL; + for (i = 0; i < MAX_WAYPOINTS; i++) { + botai_waypoints[i].next = botai_freewaypoints; + botai_freewaypoints = &botai_waypoints[i]; + } +} + +/* +================== +TeamPlayIsOn +================== +*/ +int TeamPlayIsOn(void) { + return ( gametype == GT_TEAM || gametype == GT_CTF ); +} + +/* +================== +BotAggression +================== +*/ +float BotAggression(bot_state_t *bs) { + //if the bot has quad + if (bs->inventory[INVENTORY_QUAD]) { + //if the bot is not holding the gauntlet or the enemy is really nearby + if (bs->inventory[ENEMY_HORIZONTAL_DIST] < 80) { + return 70; + } + } + //if the enemy is located way higher than the bot + if (bs->inventory[ENEMY_HEIGHT] > 200) return 0; + //if the bot is very low on health + if (bs->inventory[INVENTORY_HEALTH] < 60) return 0; + //if the bot is low on health + if (bs->inventory[INVENTORY_HEALTH] < 80) { + //if the bot has insufficient armor + if (bs->inventory[INVENTORY_ARMOR] < 40) return 0; + } + + if (bs->inventory[INVENTORY_DREADNOUGHT] > 0 && + bs->inventory[INVENTORY_DREADNOUGHTAMMO] > 0) return 100; + + if (bs->inventory[INVENTORY_TETRION] > 0 && + bs->inventory[INVENTORY_TETRIONAMMO] > 0) return 95; + + if (bs->inventory[INVENTORY_QUANTUM] > 0 && + bs->inventory[INVENTORY_QUANTUMAMMO] > 0) return 90; + + if (bs->inventory[INVENTORY_STASIS] > 0 && + bs->inventory[INVENTORY_STASISAMMO] > 0) return 85; + + if (bs->inventory[INVENTORY_SCAVENGER] > 0 && + bs->inventory[INVENTORY_SCAVENGERAMMO] > 0) return 80; + + if (bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 && + bs->inventory[INVENTORY_GRENADES] > 0) return 75; + + if (bs->inventory[INVENTORY_IMOD] > 0 && + bs->inventory[INVENTORY_IMODAMMO] > 0) return 70; + + if (bs->inventory[INVENTORY_COMPRESSION] > 0 && + bs->inventory[INVENTORY_COMPRESSIONAMMO] > 0) return 65; + + //otherwise the bot is not feeling too aggressive + return 0; +} + +/* +================== +BotWantsToRetreat +================== +*/ +int BotWantsToRetreat(bot_state_t *bs) { + aas_entityinfo_t entinfo; + + //always retreat when carrying a CTF flag + if (BotCTFCarryingFlag(bs)) return qtrue; + // + if (bs->enemy >= 0) { + //if the enemy is carrying a flag + BotEntityInfo(bs->enemy, &entinfo); + if (EntityCarriesFlag(&entinfo)) return qfalse; + } + //if the bot is getting the flag + if (bs->ltgtype == LTG_GETFLAG) return qtrue; + // + if (BotAggression(bs) < 50) return qtrue; + return qfalse; +} + +/* +================== +BotWantsToChase +================== +*/ +int BotWantsToChase(bot_state_t *bs) { + aas_entityinfo_t entinfo; + + //always retreat when carrying a CTF flag + if (BotCTFCarryingFlag(bs)) return qfalse; + //if the enemy is carrying a flag + BotEntityInfo(bs->enemy, &entinfo); + if (EntityCarriesFlag(&entinfo)) return qtrue; + //if the bot is getting the flag + if (bs->ltgtype == LTG_GETFLAG) return qfalse; + // + if (BotAggression(bs) > 50) return qtrue; + return qfalse; +} + +/* +================== +BotWantsToHelp +================== +*/ +int BotWantsToHelp(bot_state_t *bs) { + return qtrue; +} + +/* +================== +BotCanAndWantsToRocketJump +================== +*/ +int BotCanAndWantsToRocketJump(bot_state_t *bs) { + float rocketjumper; + + //if rocket jumping is disabled + if (!bot_rocketjump.integer) return qfalse; + //if no rocket launcher + if (bs->inventory[INVENTORY_QUANTUM] <= 0) return qfalse; + //if low on rockets + if (bs->inventory[INVENTORY_QUANTUMAMMO] < 1) return qfalse; + //never rocket jump with the Quad + if (bs->inventory[INVENTORY_QUAD]) + { + if ( rpg_selfdamage.integer != 0 ) + { + return qfalse; + } + } + //if low on health + if (bs->inventory[INVENTORY_HEALTH] < 50) + { //if not full health + if ( rpg_selfdamage.integer != 0 ) + { + return qfalse; + } + } + if (bs->inventory[INVENTORY_HEALTH] < 60) + { //if the bot has insufficient armor + if (bs->inventory[INVENTORY_ARMOR] < 20) + { + if ( rpg_selfdamage.integer != 0 ) + { + return qfalse; + } + } + } + rocketjumper = 1; + return qtrue; +} + +/* +================== +BotGoCamp +================== +*/ +void BotGoCamp(bot_state_t *bs, bot_goal_t *goal) { + float camper; + + //set message time to zero so bot will NOT show any message + bs->teammessage_time = 0; + //set the ltg type + bs->ltgtype = LTG_CAMP; + //set the team goal + memcpy(&bs->teamgoal, goal, sizeof(bot_goal_t)); + //get the team goal time + camper = 0; + if (camper > 0.99) bs->teamgoal_time = 99999; + else bs->teamgoal_time = 120 + 180 * camper + random() * 15; + //set the last time the bot started camping + bs->camp_time = trap_AAS_Time(); + //the teammate that requested the camping + bs->teammate = 0; + //do NOT type arrive message + bs->arrive_time = 1; +} + +/* +================== +BotWantsToCamp +================== +*/ +int BotWantsToCamp(bot_state_t *bs) { + float camper; + camper = 0; + return qfalse; +} + +/* +================== +BotDontAvoid +================== +*/ +void BotDontAvoid(bot_state_t *bs, char *itemname) { + bot_goal_t goal; + int num; + + num = trap_BotGetLevelItemGoal(-1, itemname, &goal); + while(num >= 0) { + trap_BotRemoveFromAvoidGoals(bs->gs, goal.number); + num = trap_BotGetLevelItemGoal(num, itemname, &goal); + } +} + +/* +================== +BotGoForPowerups +================== +*/ +void BotGoForPowerups(bot_state_t *bs) { + + //don't avoid any of the powerups anymore + BotDontAvoid(bs, "Quantum Weapon Enhancer"); + BotDontAvoid(bs, "Nano-Regenerative Protoplasmer"); + BotDontAvoid(bs, "Metaphasic Shielding"); + BotDontAvoid(bs, "Temporal Accelerator"); + BotDontAvoid(bs, "Personal Cloaking Device"); + BotDontAvoid(bs, "Seeker Drone"); + BotDontAvoid(bs, "Anti-Gravity Pack"); + //reset the long term goal time so the bot will go for the powerup + //NOTE: the long term goal type doesn't change + bs->ltg_time = 0; +} + +/* +================== +BotRoamGoal +================== +*/ +void BotRoamGoal(bot_state_t *bs, vec3_t goal) { + int pc, i; + float len, rnd; + vec3_t dir, bestorg, belowbestorg; + bsp_trace_t trace; + + for (i = 0; i < 10; i++) { + //start at the bot origin + VectorCopy(bs->origin, bestorg); + rnd = random(); + if (rnd > 0.25) { + //add a random value to the x-coordinate + if (random() < 0.5) bestorg[0] -= 800 * random() + 100; + else bestorg[0] += 800 * random() + 100; + } + if (rnd < 0.75) { + //add a random value to the y-coordinate + if (random() < 0.5) bestorg[1] -= 800 * random() + 100; + else bestorg[1] += 800 * random() + 100; + } + //add a random value to the z-coordinate (NOTE: 48 = maxjump?) + bestorg[2] += 2 * 48 * crandom(); + //trace a line from the origin to the roam target + BotAI_Trace(&trace, bs->origin, NULL, NULL, bestorg, bs->entitynum, MASK_SOLID); + //direction and length towards the roam target + VectorSubtract(trace.endpos, bs->origin, dir); + len = VectorNormalize(dir); + //if the roam target is far away anough + if (len > 200) { + //the roam target is in the given direction before walls + VectorScale(dir, len * trace.fraction - 40, dir); + VectorAdd(bs->origin, dir, bestorg); + //get the coordinates of the floor below the roam target + belowbestorg[0] = bestorg[0]; + belowbestorg[1] = bestorg[1]; + belowbestorg[2] = bestorg[2] - 800; + BotAI_Trace(&trace, bestorg, NULL, NULL, belowbestorg, bs->entitynum, MASK_SOLID); + // + if (!trace.startsolid) { + trace.endpos[2]++; + pc = trap_PointContents(trace.endpos, bs->entitynum); + if (!(pc & (CONTENTS_LAVA | CONTENTS_SLIME))) { + VectorCopy(bestorg, goal); + return; + } + } + } + } + VectorCopy(bestorg, goal); +} + +/* +================== +BotAttackMove +================== +*/ +bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl) { + int movetype, i; + float attack_skill, jumper, croucher, dist, strafechange_time; + float attack_dist, attack_range; + vec3_t forward, backward, sideward, hordir, up = {0, 0, 1}; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + bot_goal_t goal; + + if (bs->attackchase_time > trap_AAS_Time()) { + //create the chase goal + goal.entitynum = bs->enemy; + goal.areanum = bs->lastenemyareanum; + VectorCopy(bs->lastenemyorigin, goal.origin); + VectorSet(goal.mins, -8, -8, -8); + VectorSet(goal.maxs, 8, 8, 8); + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, tfl); + return moveresult; + } + // + memset(&moveresult, 0, sizeof(bot_moveresult_t)); + // + attack_skill = 1; + jumper = 1; + croucher = 1; + //initialize the movement state + BotSetupForMovement(bs); + //get the enemy entity info + BotEntityInfo(bs->enemy, &entinfo); + //direction towards the enemy + VectorSubtract(entinfo.origin, bs->origin, forward); + //the distance towards the enemy + dist = VectorNormalize(forward); + VectorNegate(forward, backward); + //walk, crouch or jump + movetype = MOVE_WALK; + // + if (bs->attackcrouch_time < trap_AAS_Time() - 1) { + if (random() < jumper) { + movetype = MOVE_JUMP; + } + //wait at least one second before crouching again + else if (bs->attackcrouch_time < trap_AAS_Time() - 1 && random() < croucher) { + bs->attackcrouch_time = trap_AAS_Time() + croucher * 5; + } + } + if (bs->attackcrouch_time > trap_AAS_Time()) movetype = MOVE_CROUCH; + //if the bot should jump + if (movetype == MOVE_JUMP) { + //if jumped last frame + if (bs->attackjump_time > trap_AAS_Time()) { + movetype = MOVE_WALK; + } + else { + bs->attackjump_time = trap_AAS_Time() + 1; + } + } + + //if using assimilator or alt-fire hypo... + if ( bs->weaponnum == WP_TOOLKIT || bs->weaponnum == (WP_VOYAGER_HYPO+WP_NUM_WEAPONS) ) + {//get real close + attack_dist = 16; + attack_range = 16; + } + else + { + attack_dist = IDEAL_ATTACKDIST; + attack_range = 40; + } + + //increase the strafe time + bs->attackstrafe_time += bs->thinktime; + //get the strafe change time + strafechange_time = 0.4 + (1 - attack_skill) * 0.2; + if (attack_skill > 0.7) strafechange_time += crandom() * 0.2; + //if the strafe direction should be changed + if (bs->attackstrafe_time > strafechange_time) { + //some magic number :) + if (random() > 0.935) { + //flip the strafe direction + bs->flags ^= BFL_STRAFERIGHT; + bs->attackstrafe_time = 0; + } + } + // + for (i = 0; i < 2; i++) { + hordir[0] = forward[0]; + hordir[1] = forward[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //get the sideward vector + CrossProduct(hordir, up, sideward); + //reverse the vector depending on the strafe direction + if (bs->flags & BFL_STRAFERIGHT) VectorNegate(sideward, sideward); + //randomly go back a little + if (random() > 0.9) { + VectorAdd(sideward, backward, sideward); + } + else { + //walk forward or backward to get at the ideal attack distance + if (dist > attack_dist + attack_range) VectorAdd(sideward, forward, sideward); + else if (dist < attack_dist - attack_range) VectorAdd(sideward, backward, sideward); + } + //perform the movement + if (trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) return moveresult; + //movement failed, flip the strafe direction + bs->flags ^= BFL_STRAFERIGHT; + bs->attackstrafe_time = 0; + } + //bot couldn't do any usefull movement +// bs->attackchase_time = AAS_Time() + 6; + return moveresult; +} + +/* +================== +BotSameTeam +================== +*/ +int BotSameTeam(bot_state_t *bs, int entnum) { + char info1[1024], info2[1024]; + + if (bs->client < 0 || bs->client >= MAX_CLIENTS) { + //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); + return qfalse; + } + if (entnum < 0 || entnum >= MAX_CLIENTS) { + //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); + return qfalse; + } + if (gametype == GT_TEAM || gametype == GT_CTF) { + trap_GetConfigstring(CS_PLAYERS+bs->client, info1, sizeof(info1)); + trap_GetConfigstring(CS_PLAYERS+entnum, info2, sizeof(info2)); + // + if (atoi(Info_ValueForKey(info1, "t")) == atoi(Info_ValueForKey(info2, "t"))) return qtrue; + } + return qfalse; +} + +/* +================== +InFieldOfVision +================== +*/ +qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles) +{ + int i; + float diff, angle; + + for (i = 0; i < 2; i++) { + angle = AngleMod(viewangles[i]); + angles[i] = AngleMod(angles[i]); + diff = angles[i] - angle; + if (angles[i] > angle) { + if (diff > 180.0) diff -= 360.0; + } + else { + if (diff < -180.0) diff += 360.0; + } + if (diff > 0) { + if (diff > fov * 0.5) return qfalse; + } + else { + if (diff < -fov * 0.5) return qfalse; + } + } + return qtrue; +} + +/* +================== +BotEntityVisible + +returns visibility in the range [0, 1] taking fog and water surfaces into account +================== +*/ +float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent) { + int i, contents_mask, passent, hitent, infog, inwater, otherinfog, pc; + float fogdist, waterfactor, vis, bestvis; + bsp_trace_t trace; + aas_entityinfo_t entinfo; + vec3_t dir, entangles, start, end, middle; + + //calculate middle of bounding box + BotEntityInfo(ent, &entinfo); + VectorAdd(entinfo.mins, entinfo.maxs, middle); + VectorScale(middle, 0.5, middle); + VectorAdd(entinfo.origin, middle, middle); + //check if entity is within field of vision + VectorSubtract(middle, eye, dir); + vectoangles(dir, entangles); + if (!InFieldOfVision(viewangles, fov, entangles)) return 0; + // + pc = trap_AAS_PointContents(eye); + infog = (pc & CONTENTS_SOLID); + inwater = (pc & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)); + // + bestvis = 0; + for (i = 0; i < 3; i++) { + //if the point is not in potential visible sight + //if (!AAS_inPVS(eye, middle)) continue; + // + contents_mask = CONTENTS_SOLID|CONTENTS_PLAYERCLIP; + passent = viewer; + hitent = ent; + VectorCopy(eye, start); + VectorCopy(middle, end); + //if the entity is in water, lava or slime + if (trap_AAS_PointContents(middle) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) { + contents_mask |= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); + } + //if eye is in water, lava or slime + if (inwater) { + if (!(contents_mask & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) { + passent = ent; + hitent = viewer; + VectorCopy(middle, start); + VectorCopy(eye, end); + } + contents_mask ^= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); + } + //trace from start to end + BotAI_Trace(&trace, start, NULL, NULL, end, passent, contents_mask); + //if water was hit + waterfactor = 1.0; + if (trace.contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) { + //if the water surface is translucent + if (1) { + //trace through the water + contents_mask &= ~(CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); + BotAI_Trace(&trace, trace.endpos, NULL, NULL, end, passent, contents_mask); + waterfactor = 0.5; + } + } + //if a full trace or the hitent was hit + if (trace.fraction >= 1 || trace.ent == hitent) { + //check for fog, assuming there's only one fog brush where + //either the viewer or the entity is in or both are in + otherinfog = (trap_AAS_PointContents(middle) & CONTENTS_FOG); + if (infog && otherinfog) { + VectorSubtract(trace.endpos, eye, dir); + fogdist = VectorLength(dir); + } + else if (infog) { + VectorCopy(trace.endpos, start); + BotAI_Trace(&trace, start, NULL, NULL, eye, viewer, CONTENTS_FOG); + VectorSubtract(eye, trace.endpos, dir); + fogdist = VectorLength(dir); + } + else if (otherinfog) { + VectorCopy(trace.endpos, end); + BotAI_Trace(&trace, eye, NULL, NULL, end, viewer, CONTENTS_FOG); + VectorSubtract(end, trace.endpos, dir); + fogdist = VectorLength(dir); + } + else { + //if the entity and the viewer are not in fog assume there's no fog in between + fogdist = 0; + } + //decrease visibility with the view distance through fog + vis = 1 / ((fogdist * fogdist * 0.001) < 1 ? 1 : (fogdist * fogdist * 0.001)); + //if entering water visibility is reduced + vis *= waterfactor; + // + if (vis > bestvis) bestvis = vis; + //if pretty much no fog + if (bestvis >= 0.95) return bestvis; + } + //check bottom and top of bounding box as well + if (i == 0) middle[2] += entinfo.mins[2]; + else if (i == 1) middle[2] += entinfo.maxs[2] - entinfo.mins[2]; + } + return bestvis; +} + +/* +================== +BotFindEnemy +================== +*/ +int BotFindEnemy(bot_state_t *bs, int curenemy) { + int i, healthdecrease; + float f, dist, curdist, alertness, easyfragger, vis; + aas_entityinfo_t entinfo, curenemyinfo; + vec3_t dir, angles; + + alertness = 1; + easyfragger = 1; + //check if the health decreased + healthdecrease = bs->lasthealth > bs->inventory[INVENTORY_HEALTH]; + //remember the current health value + bs->lasthealth = bs->inventory[INVENTORY_HEALTH]; + // + if (curenemy >= 0) { + BotEntityInfo(curenemy, &curenemyinfo); + if (EntityCarriesFlag(&curenemyinfo)) return qfalse; + VectorSubtract(curenemyinfo.origin, bs->origin, dir); + curdist = VectorLength(dir); + } + else { + curdist = 0; + } + // + + //FIXME: This only finds lowest numbered enemy, not the closest or best!!! + // 6/15/00 dpk changed this + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + + if (i == bs->client) continue; + //if it's the current enemy + if (i == curenemy) continue; + // + BotEntityInfo(i, &entinfo); + // + if (!entinfo.valid) continue; + //if on the same team + if (BotSameTeam(bs, i)) continue; + //if the enemy isn't dead and the enemy isn't the bot self + if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; + //if the enemy is invisible and not shooting + if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { + continue; + } + //if not an easy fragger don't shoot at chatting players + if (easyfragger < 0.5 && EntityIsChatting(&entinfo)) continue; + // + if (lastteleport_time > trap_AAS_Time() - 3) { + VectorSubtract(entinfo.origin, lastteleport_origin, dir); + if (VectorLength(dir) < 70) continue; + } + //calculate the distance towards the enemy + VectorSubtract(entinfo.origin, bs->origin, dir); + dist = VectorLength(dir); + + + //if this entity is not carrying a flag + if (!EntityCarriesFlag(&entinfo)) +// if (EntityCarriesFlag(&entinfo)) +/* { + // pick this one! + } + else +*/ { + //if this enemy is further away than the current one + if (curenemy >= 0 && dist > curdist) continue; + } + + //if the bot in too far away for me to notice + if (dist > 900 + alertness * 4000) continue; + + + //if the bot's health decreased or the enemy is shooting + if (curenemy < 0 && (healthdecrease || EntityIsShooting(&entinfo))) f = 360; + else f = 90 + 90 - (90 - (dist > 810 ? 810 : dist) / 9); + //check if the enemy is visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, f, i); + if (vis <= 0) continue; + //if the enemy is quite far away, not shooting and the bot is not damaged + if (curenemy < 0 && dist > 200 && !healthdecrease && !EntityIsShooting(&entinfo)) + { + //check if we can avoid this enemy + VectorSubtract(bs->origin, entinfo.origin, dir); + vectoangles(dir, angles); + //if the bot isn't in the fov of the enemy + if (!InFieldOfVision(entinfo.angles, 120, angles)) { + //update some stuff for this enemy + BotUpdateBattleInventory(bs, i); + //if the bot doesn't really want to fight + if (BotWantsToRetreat(bs)) continue; + } + } +// } + //found an enemy + bs->enemy = entinfo.number; + if (curenemy >= 0) bs->enemysight_time = trap_AAS_Time() - 2; + else bs->enemysight_time = trap_AAS_Time(); + bs->enemysuicide = qfalse; + bs->enemydeath_time = 0; + return qtrue; + } + return qfalse; +} + +/* +================== +BotTeamFlagCarrierVisible +================== +*/ +int BotTeamFlagCarrierVisible(bot_state_t *bs) { + int i; + float vis; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) continue; + //if this player is carrying a flag + if (!EntityCarriesFlag(&entinfo)) continue; + //if the flag carrier is not on the same team + if (!BotSameTeam(bs, i)) continue; + //if the flag carrier is not visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis <= 0) continue; + // + return i; + } + return -1; +} + +/* +================== +BotAimAtEnemy + + MCG - FIXME: This is not set up at all correctly in the following areas: + Needs to consider if weapon is an alt weapon for aim and accuracy as well as functionality + Need to consider range? + Grenade Primary and alt as well as Scavenger alt need to take into account gravity + Needs to pick our new weapons correctly to determine which ones they should: + lead with (projectiles have speed) + decay aim purposely (instant hit weaps) + shoot "around corners" (bouncers, mines, splashdamage?) + +================== +*/ +void BotAimAtEnemy(bot_state_t *bs) { + int i, enemyvisible; + float dist, f, aim_skill, aim_accuracy, speed, reactiontime; + vec3_t dir, bestorigin, end, start, groundtarget, cmdmove, enemyvelocity; + vec3_t mins = {-4,-4,-4}, maxs = {4, 4, 4}; + weaponinfo_t wi; + aas_entityinfo_t entinfo; + bot_goal_t goal; + bsp_trace_t trace; + vec3_t target; + + //if the bot has no enemy + if (bs->enemy < 0) return; + // + //BotAI_Print(PRT_MESSAGE, "client %d: aiming at client %d\n", bs->entitynum, bs->enemy); + // + aim_skill = 1; + aim_accuracy = 1; + // + if (aim_skill > 0.95) + { + //don't aim too early + reactiontime = 0.5; + if (bs->enemysight_time > trap_AAS_Time() - reactiontime) return; + if (bs->teleport_time > trap_AAS_Time() - reactiontime) return; + } + + //get the weapon information + trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi); + //get the weapon specific aim accuracy and or aim skill + + if (wi.number == WP_GRENADE_LAUNCHER) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER, 0, 1); + aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER, 0, 1); + } + if (wi.number == WP_DISRUPTOR) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_STASIS, 0, 1); + aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_STASIS, 0, 1); + } + if (wi.number == WP_PHASER) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_PHASER, 0, 1); + } + if (wi.number == WP_NULL_HAND) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_IMOD, 0, 1); + } + if (wi.number == WP_COMPRESSION_RIFLE) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_COMPRESSION, 0, 1); + } + if (wi.number == WP_TR116) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_TETRION, 0, 1); + } + if (wi.number == WP_DERMAL_REGEN) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_DREADNOUGHT, 0, 1); + } + if (wi.number == WP_QUANTUM_BURST) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_QUANTUM, 0, 1); + aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_QUANTUM, 0, 1); + } + if (wi.number == WP_COFFEE) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_SCAVENGER, 0, 1); + } + + // + if (aim_accuracy <= 0) aim_accuracy = 0.0001; + //get the enemy entity information + BotEntityInfo(bs->enemy, &entinfo); + //if the enemy is invisible then shoot crappy most of the time + if (EntityIsInvisible(&entinfo)) { + if (random() > 0.1) aim_accuracy *= 0.4; + } + // + VectorSubtract(entinfo.origin, entinfo.lastvisorigin, enemyvelocity); + VectorScale(enemyvelocity, 1 / entinfo.update_time, enemyvelocity); + //enemy origin and velocity is remembered every 0.5 seconds + if (bs->enemyposition_time < trap_AAS_Time()) { + // + bs->enemyposition_time = trap_AAS_Time() + 0.5; + VectorCopy(enemyvelocity, bs->enemyvelocity); + VectorCopy(entinfo.origin, bs->enemyorigin); + } + //if not extremely skilled + if (aim_skill < 0.9) { + VectorSubtract(entinfo.origin, bs->enemyorigin, dir); + //if the enemy moved a bit + if (VectorLength(dir) > 48) { + //if the enemy changed direction + if (DotProduct(bs->enemyvelocity, enemyvelocity) < 0) { + //aim accuracy should be worse now + aim_accuracy *= 0.7; + } + } + } + //check visibility of enemy + enemyvisible = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy); + //if the enemy is visible + if (enemyvisible) { + // + VectorCopy(entinfo.origin, bestorigin); + bestorigin[2] += 8; + //get the start point shooting from + //NOTE: the x and y projectile start offsets are ignored + VectorCopy(bs->origin, start); + start[2] += bs->cur_ps.viewheight; + start[2] += wi.offset[2]; + // + BotAI_Trace(&trace, start, mins, maxs, bestorigin, bs->entitynum, MASK_SHOT); + //if the enemy is NOT hit + if (trace.fraction <= 1 && trace.ent != entinfo.number) { + bestorigin[2] += 16; + } + //if it is not an instant hit weapon the bot might want to predict the enemy + if (wi.speed) { + // + VectorSubtract(bestorigin, bs->origin, dir); + dist = VectorLength(dir); + VectorSubtract(entinfo.origin, bs->enemyorigin, dir); + //if the enemy is NOT pretty far away and strafing just small steps left and right + if (!(dist > 100 && VectorLength(dir) < 32)) { + //if skilled anough do exact prediction + if (aim_skill > 0.8 && + //if the weapon is ready to fire + bs->cur_ps.weaponstate == WEAPON_READY) { + aas_clientmove_t move; + vec3_t origin; + + VectorSubtract(entinfo.origin, bs->origin, dir); + //distance towards the enemy + dist = VectorLength(dir); + //direction the enemy is moving in + VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); + // + VectorScale(dir, 1 / entinfo.update_time, dir); + // + VectorCopy(entinfo.origin, origin); + origin[2] += 1; + // + VectorClear(cmdmove); + //AAS_ClearShownDebugLines(); + trap_AAS_PredictClientMovement(&move, bs->enemy, origin, + PRESENCE_CROUCH, qfalse, + dir, cmdmove, 0, + dist * 10 / wi.speed, 0.1, 0, 0, qfalse); + VectorCopy(move.endpos, bestorigin); + //BotAI_Print(PRT_MESSAGE, "%1.1f predicted speed = %f, frames = %f\n", trap_AAS_Time(), VectorLength(dir), dist * 10 / wi.speed); + } + //if not that skilled do linear prediction + else if (aim_skill > 0.4) { + VectorSubtract(entinfo.origin, bs->origin, dir); + //distance towards the enemy + dist = VectorLength(dir); + //direction the enemy is moving in + VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); + dir[2] = 0; + // + speed = VectorNormalize(dir) / entinfo.update_time; + //botimport.Print(PRT_MESSAGE, "speed = %f, wi->speed = %f\n", speed, wi->speed); + //best spot to aim at + VectorMA(entinfo.origin, (dist / wi.speed) * speed, dir, bestorigin); + } + } + } + //if the projectile does radial damage + if (aim_skill > 0.6 && wi.proj.damagetype & DAMAGETYPE_RADIAL) { + //if the enemy isn't standing significantly higher than the bot + if (entinfo.origin[2] < bs->origin[2] + 16) { + //try to aim at the ground in front of the enemy + VectorCopy(entinfo.origin, end); + end[2] -= 64; + BotAI_Trace(&trace, entinfo.origin, NULL, NULL, end, entinfo.number, MASK_SHOT); + // + VectorCopy(bestorigin, groundtarget); + if (trace.startsolid) groundtarget[2] = entinfo.origin[2] - 16; + else groundtarget[2] = trace.endpos[2] - 8; + //trace a line from projectile start to ground target + BotAI_Trace(&trace, start, NULL, NULL, groundtarget, bs->entitynum, MASK_SHOT); + //if hitpoint is not vertically too far from the ground target + if (fabs(trace.endpos[2] - groundtarget[2]) < 50) { + VectorSubtract(trace.endpos, groundtarget, dir); + //if the hitpoint is near anough the ground target + if (VectorLength(dir) < 60) { + VectorSubtract(trace.endpos, start, dir); + //if the hitpoint is far anough from the bot + if (VectorLength(dir) > 100) { + //check if the bot is visible from the ground target + trace.endpos[2] += 1; + BotAI_Trace(&trace, trace.endpos, NULL, NULL, entinfo.origin, entinfo.number, MASK_SHOT); + if (trace.fraction >= 1) { + //botimport.Print(PRT_MESSAGE, "%1.1f aiming at ground\n", AAS_Time()); + VectorCopy(groundtarget, bestorigin); + } + } + } + } + } + } + bestorigin[0] += 20 * crandom() * (1 - aim_accuracy); + bestorigin[1] += 20 * crandom() * (1 - aim_accuracy); + bestorigin[2] += 10 * crandom() * (1 - aim_accuracy); + } + else { + // + VectorCopy(bs->lastenemyorigin, bestorigin); + bestorigin[2] += 8; + //if the bot is skilled anough + if (aim_skill > 0.5) { + //do prediction shots around corners + if (wi.number == WP_DISRUPTOR || + wi.number == WP_GRENADE_LAUNCHER) + { + //create the chase goal + goal.entitynum = bs->client; + goal.areanum = bs->areanum; + VectorCopy(bs->eye, goal.origin); + VectorSet(goal.mins, -8, -8, -8); + VectorSet(goal.maxs, 8, 8, 8); + // + if (trap_BotPredictVisiblePosition(bs->lastenemyorigin, bs->lastenemyareanum, &goal, TFL_DEFAULT, target)) { + VectorSubtract(target, bs->eye, dir); + if (VectorLength(dir) > 80) { + VectorCopy(target, bestorigin); + bestorigin[2] -= 20; + } + } + aim_accuracy = 1; + } + } + } + // + if (enemyvisible) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, bestorigin, bs->entitynum, MASK_SHOT); + VectorCopy(trace.endpos, bs->aimtarget); + } + else { + VectorCopy(bestorigin, bs->aimtarget); + } + //get aim direction + VectorSubtract(bestorigin, bs->eye, dir); + // kef -- fixme. i'm guessing this is listing all of the instant-hit weapons? + if (wi.number == WP_PHASER || + wi.number == WP_NULL_HAND) { + //distance towards the enemy + dist = VectorLength(dir); + if (dist > 150) dist = 150; + f = 0.6 + dist / 150 * 0.4; + aim_accuracy *= f; + } + //add some random stuff to the aim direction depending on the aim accuracy + if (aim_accuracy < 0.8) { + VectorNormalize(dir); + for (i = 0; i < 3; i++) dir[i] += 0.3 * crandom() * (1 - aim_accuracy); + } + //set the ideal view angles + vectoangles(dir, bs->ideal_viewangles); + //take the weapon spread into account for lower skilled bots + bs->ideal_viewangles[PITCH] += 6 * wi.vspread * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]); + bs->ideal_viewangles[YAW] += 6 * wi.hspread * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]); + //if the bots should be really challenging + //if the bot is really accurate and has the enemy in view for some time + if (aim_accuracy > 0.9 && bs->enemysight_time < trap_AAS_Time() - 1) + { + //set the view angles directly + if (bs->ideal_viewangles[PITCH] > 180) + { + bs->ideal_viewangles[PITCH] -= 360; + } + VectorCopy(bs->ideal_viewangles, bs->viewangles); + trap_EA_View(bs->client, bs->viewangles); + } +} + +/* +================== +BotCheckAttack +================== +*/ +void BotCheckAttack(bot_state_t *bs) { + float points, reactiontime, fov, firethrottle; + bsp_trace_t bsptrace; + //float selfpreservation; + vec3_t forward, right, start, end, dir, angles; + weaponinfo_t wi; + bsp_trace_t trace; + aas_entityinfo_t entinfo; + vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; + + if (bs->enemy < 0) return; + // + reactiontime = 0; + if (bs->enemysight_time > trap_AAS_Time() - reactiontime) return; + if (bs->teleport_time > trap_AAS_Time() - reactiontime) return; + //if changing weapons + if (bs->weaponchange_time > trap_AAS_Time() - 0.1) return; + //check fire throttle characteristic + if (bs->firethrottlewait_time > trap_AAS_Time()) return; + firethrottle = 1; + if (bs->firethrottleshoot_time < trap_AAS_Time()) { + if (random() > firethrottle) { + bs->firethrottlewait_time = trap_AAS_Time() + firethrottle; + bs->firethrottleshoot_time = 0; + } + else { + bs->firethrottleshoot_time = trap_AAS_Time() + 1 - firethrottle; + bs->firethrottlewait_time = 0; + } + } + // + BotEntityInfo(bs->enemy, &entinfo); + VectorSubtract(entinfo.origin, bs->eye, dir); + // + if (VectorLength(dir) < 100) fov = 120; + else fov = 50; + /* + //if the enemy isn't visible + if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, fov, bs->enemy)) { + //botimport.Print(PRT_MESSAGE, "enemy not visible\n"); + return; + }*/ + vectoangles(dir, angles); + if (!InFieldOfVision(bs->viewangles, fov, angles)) return; + BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->aimtarget, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (bsptrace.fraction < 1 && bsptrace.ent != bs->enemy) return; + + //get the weapon info + trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi); + //get the start point shooting from + VectorCopy(bs->origin, start); + start[2] += bs->cur_ps.viewheight; + AngleVectors(bs->viewangles, forward, right, NULL); + start[0] += forward[0] * wi.offset[0] + right[0] * wi.offset[1]; + start[1] += forward[1] * wi.offset[0] + right[1] * wi.offset[1]; + start[2] += forward[2] * wi.offset[0] + right[2] * wi.offset[1] + wi.offset[2]; + //end point aiming at + VectorMA(start, 1000, forward, end); + //a little back to make sure not inside a very close enemy + VectorMA(start, -12, forward, start); + BotAI_Trace(&trace, start, mins, maxs, end, bs->entitynum, MASK_SHOT); + //if won't hit the enemy + if (trace.ent != bs->enemy) { + //if the entity is a client + if (trace.ent > 0 && trace.ent <= MAX_CLIENTS) { + //if a teammate is hit + if (BotSameTeam(bs, trace.ent)) return; + } + //if the projectile does a radial damage + if (wi.proj.damagetype & DAMAGETYPE_RADIAL) { + if (trace.fraction * 1000 < wi.proj.radius) { + points = (wi.proj.damage - 0.5 * trace.fraction * 1000) * 0.5; + if (points > 0) { +// selfpreservation = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_SELFPRESERVATION, 0, 1); +// if (random() < selfpreservation) return; + return; + } + } + //FIXME: check if a teammate gets radial damage + } + } + //if fire has to be release to activate weapon + if (wi.flags & WFL_FIRERELEASED) { + if (bs->flags & BFL_ATTACKED) + {// check in here to either call the alt_fire or not!! also, adjust the bots weapon... hackery? + if (bs->weaponnum > WP_NUM_WEAPONS) // it's an alt_fire! + { + bs->weaponnum -= WP_NUM_WEAPONS; + trap_EA_Alt_Attack(bs->client); + } + else + { + trap_EA_Attack(bs->client); + } + } + } + else + { // check in here to either call the alt_fire or not!! also, adjust the bots weapon... hackery? + if (bs->weaponnum > WP_NUM_WEAPONS) // it's an alt_fire! + { + bs->weaponnum -= WP_NUM_WEAPONS; + trap_EA_Alt_Attack(bs->client); + } + else + { + trap_EA_Attack(bs->client); + } + } + bs->flags ^= BFL_ATTACKED; +} + +/* +================== +BotMapScripts +================== +*/ +void BotMapScripts(bot_state_t *bs) { + char info[1024]; + char mapname[128]; + int i, shootbutton; + float aim_accuracy; + aas_entityinfo_t entinfo; + vec3_t dir; + + trap_GetServerinfo(info, sizeof(info)); + + strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1); + mapname[sizeof(mapname)-1] = '\0'; + + if (!Q_stricmp(mapname, "q3tourney6")) { + vec3_t mins = {700, 204, 672}, maxs = {964, 468, 680}; + vec3_t buttonorg = {304, 352, 920}; + //NOTE: NEVER use the func_bobbing in q3tourney6 + bs->tfl &= ~TFL_FUNCBOB; + //if the bot is below the bounding box + if (bs->origin[0] > mins[0] && bs->origin[0] < maxs[0]) { + if (bs->origin[1] > mins[1] && bs->origin[1] < maxs[1]) { + if (bs->origin[2] < mins[2]) { + return; + } + } + } + shootbutton = qfalse; + //if an enemy is below this bounding box then shoot the button + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + + if (i == bs->client) continue; + // + BotEntityInfo(i, &entinfo); + // + if (!entinfo.valid) continue; + //if the enemy isn't dead and the enemy isn't the bot self + if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; + // + if (entinfo.origin[0] > mins[0] && entinfo.origin[0] < maxs[0]) { + if (entinfo.origin[1] > mins[1] && entinfo.origin[1] < maxs[1]) { + if (entinfo.origin[2] < mins[2]) { + //if there's a team mate below the crusher + if (BotSameTeam(bs, i)) { + shootbutton = qfalse; + break; + } + else { + shootbutton = qtrue; + } + } + } + } + } + if (shootbutton) { + bs->flags |= BFL_IDEALVIEWSET; + VectorSubtract(buttonorg, bs->eye, dir); + vectoangles(dir, bs->ideal_viewangles); + aim_accuracy = 1; + bs->ideal_viewangles[PITCH] += 8 * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]); + bs->ideal_viewangles[YAW] += 8 * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]); + // + if (InFieldOfVision(bs->viewangles, 20, bs->ideal_viewangles)) + {// check in here to either call the alt_fire or not!! also, adjust the bots weapon... hackery? + if (bs->weaponnum > WP_NUM_WEAPONS) // it's an alt_fire! + { + bs->weaponnum -= WP_NUM_WEAPONS; + trap_EA_Alt_Attack(bs->client); + } + else + { + trap_EA_Attack(bs->client); + } + } + } + } +} + +/* +================== +BotEntityToActivate +================== +*/ +//#define OBSTACLEDEBUG + +int BotEntityToActivate(int entitynum) { + int i, ent, cur_entities[10]; + char model[MAX_INFO_STRING], tmpmodel[128]; + char target[128], classname[128]; + float health; + char targetname[10][128]; + aas_entityinfo_t entinfo; + + BotEntityInfo(entitynum, &entinfo); + Com_sprintf(model, sizeof( model ), "*%d", entinfo.modelindex); + for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { + if (!trap_AAS_ValueForBSPEpairKey(ent, "model", tmpmodel, sizeof(tmpmodel))) continue; + if (!strcmp(model, tmpmodel)) break; + } + if (!ent) { + BotAI_Print(PRT_ERROR, "BotEntityToActivate: no entity found with model %s\n", model); + return 0; + } + trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname)); + if (!classname) { + BotAI_Print(PRT_ERROR, "BotEntityToActivate: entity with model %s has no classname\n", model); + return 0; + } + //if it is a door + if (!strcmp(classname, "func_door")) { + if (trap_AAS_FloatForBSPEpairKey(ent, "health", &health)) { + //if health the door must be shot to open + if (health) return ent; + } + } + //get the targetname so we can find an entity with a matching target + if (!trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[0], sizeof(targetname[0]))) { +#ifdef OBSTACLEDEBUG + BotAI_Print(PRT_ERROR, "BotEntityToActivate: entity with model \"%s\" has no targetname\n", model); +#endif //OBSTACLEDEBUG + return 0; + } + //only allows single activation chains, tree-like activation can be added back in + cur_entities[0] = trap_AAS_NextBSPEntity(0); + for (i = 0; i >= 0 && i < 10;) { + for (ent = cur_entities[i]; ent; ent = trap_AAS_NextBSPEntity(ent)) { + if (!trap_AAS_ValueForBSPEpairKey(ent, "target", target, sizeof(target))) continue; + if (!strcmp(targetname[i], target)) { + cur_entities[i] = trap_AAS_NextBSPEntity(ent); + break; + } + } + if (!ent) { + BotAI_Print(PRT_ERROR, "BotEntityToActivate: no entity with target \"%s\"\n", targetname[i]); + i--; + continue; + } + if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname))) { + BotAI_Print(PRT_ERROR, "BotEntityToActivate: entity with target \"%s\" has no classname\n", targetname[i]); + continue; + } + if (!strcmp(classname, "func_button")) { + //BSP button model + return ent; + } + else if (!strcmp(classname, "trigger_multiple")) { + //invisible trigger multiple box + return ent; + } + else { + i--; + } + } + BotAI_Print(PRT_ERROR, "BotEntityToActivate: unknown activator with classname \"%s\"\n", classname); + return 0; +} + +/* +================== +BotSetMovedir +================== +*/ +vec3_t VEC_UP = {0, -1, 0}; +vec3_t MOVEDIR_UP = {0, 0, 1}; +vec3_t VEC_DOWN = {0, -2, 0}; +vec3_t MOVEDIR_DOWN = {0, 0, -1}; + +void BotSetMovedir(vec3_t angles, vec3_t movedir) { + if (VectorCompare(angles, VEC_UP)) { + VectorCopy(MOVEDIR_UP, movedir); + } + else if (VectorCompare(angles, VEC_DOWN)) { + VectorCopy(MOVEDIR_DOWN, movedir); + } + else { + AngleVectors(angles, movedir, NULL, NULL); + } +} + +/* +================== +BotModelMinsMaxs + +this is ugly +================== +*/ +void BotModelMinsMaxs(int modelindex, int eType, vec3_t mins, vec3_t maxs) { + gentity_t *ent; + int i; + + ent = &g_entities[0]; + for (i = 0; i < level.num_entities; i++, ent++) { + if ( !ent->inuse ) { + continue; + } + if ( ent->s.eType != eType) { + continue; + } + if (ent->s.modelindex == modelindex) { + VectorAdd(ent->r.currentOrigin, ent->r.mins, mins); + VectorAdd(ent->r.currentOrigin, ent->r.maxs, maxs); + return; + } + } + VectorClear(mins); + VectorClear(maxs); +} + +/* +================== +BotAIBlocked + +very basic handling of bots being blocked by other entities +check what kind of entity is blocking the bot and try to activate +it otherwise try to walk around the entity +before the bot ends in this part of the AI it should predict which doors to open, +which buttons to activate etc. +================== +*/ +void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate) { + int movetype, ent, i, areas[10], numareas, modelindex; + char classname[128], model[128]; + float lip, dist, health, angle; + vec3_t hordir, size, start, end, mins, maxs, sideward, angles; + vec3_t movedir, origin, goalorigin, bboxmins, bboxmaxs; + vec3_t up = {0, 0, 1}, extramins = {1, 1, 1}, extramaxs = {-1, -1, -1}; + aas_entityinfo_t entinfo; + //bsp_trace_t bsptrace; +#ifdef OBSTACLEDEBUG + char netname[MAX_NETNAME]; + char buf[128]; +#endif + + if (!moveresult->blocked) { + bs->notblocked_time = trap_AAS_Time(); + return; + } + // + BotEntityInfo(moveresult->blockentity, &entinfo); +#ifdef OBSTACLEDEBUG + ClientName(bs->client, netname, sizeof(netname)); + BotAI_Print(PRT_MESSAGE, "%s: I'm blocked by model %d\n", netname, entinfo.modelindex); +#endif OBSTACLEDEBUG + //if blocked by a bsp model and the bot wants to activate it if possible + if (entinfo.modelindex > 0 && entinfo.modelindex <= max_bspmodelindex && activate) { + //find the bsp entity which should be activated in order to remove + //the blocking entity + ent = BotEntityToActivate(entinfo.number); + if (!ent) { + strcpy(classname, ""); +#ifdef OBSTACLEDEBUG + BotAI_Print(PRT_MESSAGE, "%s: can't find activator for blocking entity\n", ClientName(bs->client, netname, sizeof(netname))); +#endif //OBSTACLEDEBUG + } + else { + trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname)); +#ifdef OBSTACLEDEBUG + ClientName(bs->client, netname, sizeof(netname)); + BotAI_Print(PRT_MESSAGE, "%s: I should activate %s\n", netname, classname); +#endif OBSTACLEDEBUG + } + if (!strcmp(classname, "func_button")) { + //create a bot goal towards the button + trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model)); + modelindex = atoi(model+1); + //if the model is not loaded + if (!modelindex) return; + VectorClear(angles); + BotModelMinsMaxs(modelindex, ET_MOVER, mins, maxs); + //get the lip of the button + trap_AAS_FloatForBSPEpairKey(ent, "lip", &lip); + if (!lip) lip = 4; + //get the move direction from the angle + trap_AAS_FloatForBSPEpairKey(ent, "angle", &angle); + VectorSet(angles, 0, angle, 0); + BotSetMovedir(angles, movedir); + //button size + VectorSubtract(maxs, mins, size); + //button origin + VectorAdd(mins, maxs, origin); + VectorScale(origin, 0.5, origin); + //touch distance of the button + dist = fabs(movedir[0]) * size[0] + fabs(movedir[1]) * size[1] + fabs(movedir[2]) * size[2]; + dist *= 0.5; + // + trap_AAS_FloatForBSPEpairKey(ent, "health", &health); + //if the button is shootable + if (health) { + //FIXME: walk to a point where the button is visible and shoot at the button + //calculate the goal origin + VectorMA(origin, -dist, movedir, goalorigin); + // + VectorSubtract(goalorigin, bs->origin, movedir); + vectoangles(movedir, moveresult->ideal_viewangles); + moveresult->flags |= MOVERESULT_MOVEMENTVIEW; + moveresult->flags |= MOVERESULT_MOVEMENTWEAPON; + //select the machinegun and shoot + trap_EA_SelectWeapon(bs->client, WEAPONINDEX_PHASER); + if (bs->cur_ps.weapon == WEAPONINDEX_PHASER) { + trap_EA_Attack(bs->client); + } + return; + } + else { + //add bounding box size to the dist + trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs); + for (i = 0; i < 3; i++) { + if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]); + else dist += fabs(movedir[i]) * fabs(bboxmins[i]); + } + //calculate the goal origin + VectorMA(origin, -dist, movedir, goalorigin); + // + VectorCopy(goalorigin, start); + start[2] += 24; + VectorCopy(start, end); + end[2] -= 100; + numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); + // + for (i = 0; i < numareas; i++) { + if (trap_AAS_AreaReachability(areas[i])) { + break; + } + } + if (i < numareas) { + // +#ifdef OBSTACLEDEBUG + if (bs->activatemessage_time < trap_AAS_Time()) { + Com_sprintf(buf, sizeof(buf), "I have to activate a button at %1.1f %1.1f %1.1f in area %d\n", + goalorigin[0], goalorigin[1], goalorigin[2], areas[i]); + trap_EA_Say(bs->client, buf); + bs->activatemessage_time = trap_AAS_Time() + 5; + } +#endif //OBSTACLEDEBUG + // + VectorCopy(origin, bs->activategoal.origin); + bs->activategoal.areanum = areas[i]; + VectorSubtract(mins, origin, bs->activategoal.mins); + VectorSubtract(maxs, origin, bs->activategoal.maxs); + // + for (i = 0; i < 3; i++) + { + if (movedir[i] < 0) bs->activategoal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]); + else bs->activategoal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]); + } //end for + // + bs->activategoal.entitynum = entinfo.number; + bs->activategoal.number = 0; + bs->activategoal.flags = 0; + bs->activate_time = trap_AAS_Time() + 10; + AIEnter_Seek_ActivateEntity(bs); + return; + } + else { +#ifdef OBSTACLEDEBUG + if (!numareas) BotAI_Print(PRT_MESSAGE, "button not in an area\n"); + else BotAI_Print(PRT_MESSAGE, "button area has no reachabilities\n"); +#endif //OBSTACLEDEBUG + if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0; + else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0; + } + } + } + else if (!strcmp(classname, "func_door")) { + //shoot at the shootable door + trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model)); + modelindex = atoi(model+1); + //if the model is not loaded + if (!modelindex) return; + VectorClear(angles); + BotModelMinsMaxs(modelindex, ET_MOVER, mins, maxs); + //door origin + VectorAdd(mins, maxs, origin); + VectorScale(origin, 0.5, origin); + // + VectorSubtract(origin, bs->eye, movedir); + vectoangles(movedir, moveresult->ideal_viewangles); + moveresult->flags |= MOVERESULT_MOVEMENTVIEW; + moveresult->flags |= MOVERESULT_MOVEMENTWEAPON; + //select the machinegun and shoot + trap_EA_SelectWeapon(bs->client, WEAPONINDEX_PHASER); + if (bs->cur_ps.weapon == WEAPONINDEX_PHASER) { + trap_EA_Attack(bs->client); + } + return; + } + } + //just some basic dynamic obstacle avoidance code + hordir[0] = moveresult->movedir[0]; + hordir[1] = moveresult->movedir[1]; + hordir[2] = 0; + //if no direction just take a random direction + if (VectorNormalize(hordir) < 0.1) { + VectorSet(angles, 0, 360 * random(), 0); + AngleVectors(angles, hordir, NULL, NULL); + } + // + //if (moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) movetype = MOVE_JUMP; + //else + movetype = MOVE_WALK; + //if there's an obstacle at the bot's feet and head then + //the bot might be able to crouch through + VectorCopy(bs->origin, start); + start[2] += 18; + VectorMA(start, 5, hordir, end); + VectorSet(mins, -16, -16, -24); + VectorSet(maxs, 16, 16, 4); + // + //bsptrace = AAS_Trace(start, mins, maxs, end, bs->entitynum, MASK_PLAYERSOLID); + //if (bsptrace.fraction >= 1) movetype = MOVE_CROUCH; + //get the sideward vector + CrossProduct(hordir, up, sideward); + // + if (bs->flags & BFL_AVOIDRIGHT) VectorNegate(sideward, sideward); + //try to crouch straight forward? + if (movetype != MOVE_CROUCH || !trap_BotMoveInDirection(bs->ms, hordir, 400, movetype)) { + //perform the movement + if (!trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) { + //flip the avoid direction flag + bs->flags ^= BFL_AVOIDRIGHT; + //flip the direction + VectorNegate(sideward, sideward); + //move in the other direction + trap_BotMoveInDirection(bs->ms, sideward, 400, movetype); + } + } + // + if (bs->notblocked_time < trap_AAS_Time() - 0.4) { + //just reset goals and hope the bot will go into another direction + //is this still needed?? + if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0; + else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0; + } +} + +/* +================== +BotCheckConsoleMessages +================== +*/ +void BotCheckConsoleMessages(bot_state_t *bs) { + char botname[MAX_NETNAME], message[MAX_MESSAGE_SIZE], netname[MAX_NETNAME], *ptr; + float chat_reply; + int context, handle; + bot_consolemessage_t m; + bot_match_t match; + + //the name of this bot + ClientName(bs->client, botname, sizeof(botname)); + // + while((handle = trap_BotNextConsoleMessage(bs->cs, &m)) != 0) { + //if the chat state is flooded with messages the bot will read them quickly + if (trap_BotNumConsoleMessages(bs->cs) < 10) { + //if it is a chat message the bot needs some time to read it + if (m.type == CMS_CHAT && m.time > trap_AAS_Time() - (1 + random())) break; + } + // + ptr = m.message; + //if it is a chat message then don't unify white spaces and don't + //replace synonyms in the netname + if (m.type == CMS_CHAT) { + // + if (trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) { + ptr = m.message + match.variables[MESSAGE].offset; + } + } + //unify the white spaces in the message + trap_UnifyWhiteSpaces(ptr); + //replace synonyms in the right context + context = CONTEXT_NORMAL|CONTEXT_NEARBYITEM|CONTEXT_NAMES; + if (BotCTFTeam(bs) == CTF_TEAM_RED) context |= CONTEXT_CTFREDTEAM; + else context |= CONTEXT_CTFBLUETEAM; + trap_BotReplaceSynonyms(ptr, context); + //if there's no match + if (!BotMatchMessage(bs, m.message)) { + //if it is a chat message + if (m.type == CMS_CHAT && !bot_nochat.integer) { + // + if (!trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) { + trap_BotRemoveConsoleMessage(bs->cs, handle); + continue; + } + //don't use eliza chats with team messages + if (match.subtype & ST_TEAM) { + trap_BotRemoveConsoleMessage(bs->cs, handle); + continue; + } + // + trap_BotMatchVariable(&match, NETNAME, netname, sizeof(netname)); + trap_BotMatchVariable(&match, MESSAGE, message, sizeof(message)); + //if this is a message from the bot self + if (!Q_stricmp(netname, botname)) { + trap_BotRemoveConsoleMessage(bs->cs, handle); + continue; + } + //unify the message + trap_UnifyWhiteSpaces(message); + // + trap_Cvar_Update(&bot_testrchat); + if (bot_testrchat.integer) { + // + trap_BotLibVarSet("bot_testrchat", "1"); + //if bot replies with a chat message + if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY, + NULL, NULL, + NULL, NULL, + NULL, NULL, + botname, netname)) { + BotAI_Print(PRT_MESSAGE, "------------------------\n"); + } + else { + BotAI_Print(PRT_MESSAGE, "**** no valid reply ****\n"); + } + } + //if at a valid chat position and not chatting already + else if (bs->ainode != AINode_Stand && BotValidChatPosition(bs)) { + chat_reply = 0; + if (random() < 1.5 / (NumBots()+1) && random() < chat_reply) { + //if bot replies with a chat message + if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY, + NULL, NULL, + NULL, NULL, + NULL, NULL, + botname, netname)) { + //remove the console message + trap_BotRemoveConsoleMessage(bs->cs, handle); + bs->stand_time = trap_AAS_Time() + BotChatTime(bs); + AIEnter_Stand(bs); + //EA_Say(bs->client, bs->cs.chatmessage); + break; + } + } + } + } + } + //remove the console message + trap_BotRemoveConsoleMessage(bs->cs, handle); + } +} + +/* +================== +BotCheckEvents +================== +*/ +void BotCheckEvents(bot_state_t *bs, entityState_t *state) { + int event; + char buf[128]; + // + //NOTE: this sucks, we're accessing the gentity_t directly + //but there's no other fast way to do it right now + if (bs->entityeventTime[state->number] == g_entities[state->number].eventTime) { + return; + } + bs->entityeventTime[state->number] = g_entities[state->number].eventTime; + //if it's an event only entity + if (state->eType > ET_EVENTS) { + event = (state->eType - ET_EVENTS) & ~EV_EVENT_BITS; + } + else { + event = state->event & ~EV_EVENT_BITS; + } + // + switch(event) { + //client obituary event + case EV_OBITUARY: + { + int target, attacker, mod; + + target = state->otherEntityNum; + attacker = state->otherEntityNum2; + mod = state->eventParm; + // + if (target == bs->client) { + bs->botdeathtype = mod; + bs->lastkilledby = attacker; + // + if (target == attacker) bs->botsuicide = qtrue; + else bs->botsuicide = qfalse; + // + bs->num_deaths++; + } + //else if this client was killed by the bot + else if (attacker == bs->client) { + bs->enemydeathtype = mod; + bs->lastkilledplayer = target; + bs->killedenemy_time = trap_AAS_Time(); + // + bs->num_kills++; + } + else if (attacker == bs->enemy && target == attacker) { + bs->enemysuicide = qtrue; + } + break; + } + + case EV_GLOBAL_SOUND: + { + if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) { + BotAI_Print(PRT_ERROR, "EV_GLOBAL_SOUND: eventParm (%d) out of range\n", state->eventParm); + break; + } + else { + trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf)); + if (!strcmp(buf, "sound/items/poweruprespawn.wav")) { + //powerup respawned... go get it + BotGoForPowerups(bs); + } + } + break; + } + + case EV_TEAM_SOUND: + { + if (state->eventParm < 0 || state->eventParm > MAX_TEAM_SOUNDS) { + BotAI_Print(PRT_ERROR, "EV_TEAM_SOUND: eventParm (%d) out of range\n", state->eventParm); + break; + } + + if (state->eventParm == RETURN_FLAG_SOUND) + { + if (state->otherEntityNum == TEAM_RED) + { + //red flag is returned + bs->redflagstatus = 0; + bs->flagstatuschanged = qtrue; + } + else + { + //blue flag is returned + bs->blueflagstatus = 0; + bs->flagstatuschanged = qtrue; + } + } + break; + } + + + + case EV_PLAYER_TELEPORT_IN: + { + VectorCopy(state->origin, lastteleport_origin); + lastteleport_time = trap_AAS_Time(); + break; + } + case EV_GENERAL_SOUND: + { + //if this sound is played on the bot + if (state->number == bs->client) { + if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) { + BotAI_Print(PRT_ERROR, "EV_GENERAL_SOUND: eventParm (%d) out of range\n", state->eventParm); + break; + } + //check out the sound + trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf)); + //if falling into a death pit + if (!strcmp(buf, "*falling1.wav")) { + //if the bot has a personal teleporter + if (bs->inventory[INVENTORY_TRANSPORTER] > 0) { + //use the holdable item + trap_EA_Use(bs->client); + } + } + } + break; + } + } +} + +/* +================== +BotCheckSnapshot +================== +*/ +void BotCheckSnapshot(bot_state_t *bs) { + int ent; + entityState_t state; + + // + ent = 0; + while( ( ent = BotAI_GetSnapshotEntity( bs->client, ent, &state ) ) != -1 ) { + //check the entity state for events + BotCheckEvents(bs, &state); + } + //check the player state for events + BotAI_GetEntityState(bs->client, &state); + //copy the player state events to the entity state + state.event = bs->cur_ps.externalEvent; + state.eventParm = bs->cur_ps.externalEventParm; + // + BotCheckEvents(bs, &state); +} + +/* +================== +BotCheckAir +================== +*/ +void BotCheckAir(bot_state_t *bs) { + if (bs->inventory[INVENTORY_ENVIRONMENTSUIT] <= 0) { + if (trap_AAS_PointContents(bs->eye) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) { + return; + } + } + bs->lastair_time = trap_AAS_Time(); +} + +/* +================== +BotDeathmatchAI +================== +*/ +void BotDeathmatchAI(bot_state_t *bs, float thinktime) { + char gender[144], name[144], buf[144]; + char userinfo[MAX_INFO_STRING]; + int i; + + //if the bot has just been setup + if (bs->setupcount > 0) { + bs->setupcount--; + if (bs->setupcount > 0) return; + //get the gender characteristic + trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, sizeof(gender)); + //set the bot gender + trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo)); + Info_SetValueForKey(userinfo, "sex", gender); + trap_SetUserinfo(bs->client, userinfo); + //set the team + if ( g_gametype.integer != GT_TOURNAMENT ) { + Com_sprintf(buf, sizeof(buf), "team %s", bs->settings.team); + trap_EA_Command(bs->client, buf); + } + if ( g_pModSpecialties.integer ) { + Com_sprintf(buf, sizeof(buf), "class %s", bs->settings.pclass); + trap_EA_Command(bs->client, buf); + } + //set the chat gender + if (gender[0] == 'm') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE); + else if (gender[0] == 'f') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE); + else trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS); + //set the chat name + ClientName(bs->client, name, sizeof(name)); + trap_BotSetChatName(bs->cs, name); + // + bs->lastframe_health = bs->inventory[INVENTORY_HEALTH]; + bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS]; + // + bs->setupcount = 0; + } + //no ideal view set + bs->flags &= ~BFL_IDEALVIEWSET; + //set the teleport time + BotSetTeleportTime(bs); + //update some inventory values + BotUpdateInventory(bs); + //check the console messages + BotCheckConsoleMessages(bs); + //check out the snapshot + BotCheckSnapshot(bs); + //check for air + BotCheckAir(bs); + //if not in the intermission and not in observer mode + if (!BotIntermission(bs) && !BotIsObserver(bs)) { + //do team AI + BotTeamAI(bs); + } + //if the bot has no ai node + if (!bs->ainode) { + AIEnter_Seek_LTG(bs); + } + //if the bot entered the game less than 8 seconds ago + if (!bs->entergamechat && bs->entergame_time > trap_AAS_Time() - 8) { + if (BotChat_EnterGame(bs)) { + bs->stand_time = trap_AAS_Time() + BotChatTime(bs); + AIEnter_Stand(bs); + } + bs->entergamechat = qtrue; + } + //reset the node switches from the previous frame + BotResetNodeSwitches(); + //execute AI nodes + for (i = 0; i < MAX_NODESWITCHES; i++) { + if (bs->ainode(bs)) break; + } + //if the bot removed itself :) + if (!bs->inuse) return; + //if the bot executed too many AI nodes + if (i >= MAX_NODESWITCHES) { + trap_BotDumpGoalStack(bs->gs); + trap_BotDumpAvoidGoals(bs->gs); + BotDumpNodeSwitches(bs); + ClientName(bs->client, name, sizeof(name)); + BotAI_Print(PRT_ERROR, "%s at %1.1f switched more than %d AI nodes\n", name, trap_AAS_Time(), MAX_NODESWITCHES); + } + // + bs->lastframe_health = bs->inventory[INVENTORY_HEALTH]; + bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS]; +} + +/* +================== +BotSetupDeathmatchAI +================== +*/ +void BotSetupDeathmatchAI(void) { + int ent, modelnum; + char model[128]; + + gametype = trap_Cvar_VariableIntegerValue("g_gametype"); + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + trap_Cvar_Register(&bot_rocketjump, "bot_rocketjump", "1", 0); + trap_Cvar_Register(&bot_grapple, "bot_grapple", "0", 0); + trap_Cvar_Register(&bot_fastchat, "bot_fastchat", "0", 0); + trap_Cvar_Register(&bot_nochat, "bot_nochat", "0", 0); + trap_Cvar_Register(&bot_testrchat, "bot_testrchat", "0", 0); + trap_Cvar_Register(&bot_challenge, "bot_challenge", "0", 0); + // + if (gametype == GT_CTF) { + if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0) + BotAI_Print(PRT_WARNING, "CTF without Red Flag\n"); + if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0) + BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n"); + } + + max_bspmodelindex = 0; + for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { + if (!trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model))) continue; + if (model[0] == '*') { + modelnum = atoi(model+1); + if (modelnum > max_bspmodelindex) + max_bspmodelindex = modelnum; + } + } + //initialize the waypoint heap + BotInitWaypoints(); +} + +/* +================== +BotShutdownDeathmatchAI +================== +*/ +void BotShutdownDeathmatchAI(void) { +} + diff --git a/game/ai_dmq3.h b/game/ai_dmq3.h new file mode 100644 index 0000000..4769b4a --- /dev/null +++ b/game/ai_dmq3.h @@ -0,0 +1,134 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_dmq3.h + * + * desc: Quake3 bot AI + * + * $Archive: /StarTrek/Code-DM/game/ai_dmq3.h $ + * $Author: Dkramer $ + * $Revision: 2 $ + * $Modtime: 6/21/00 4:35p $ + * $Date: 6/27/00 10:06a $ + * + *****************************************************************************/ + +//!setup the deathmatch AI +void BotSetupDeathmatchAI(void); +//!shutdown the deathmatch AI +void BotShutdownDeathmatchAI(void); +//!let the bot live within it's deathmatch AI net +void BotDeathmatchAI(bot_state_t *bs, float thinktime); +//!free waypoints +void BotFreeWaypoints(bot_waypoint_t *wp); +//!choose a weapon +void BotChooseWeapon(bot_state_t *bs); +//!setup movement stuff +void BotSetupForMovement(bot_state_t *bs); +//!update the inventory +void BotUpdateInventory(bot_state_t *bs); +//!update the inventory during battle +void BotUpdateBattleInventory(bot_state_t *bs, int enemy); +//! should I detonate the detpack now +qboolean BotShouldDetonateDetPack(bot_state_t *bs); +//!use holdable items during battle +void BotBattleUseItems(bot_state_t *bs); +//!return true if the bot is dead +qboolean BotIsDead(bot_state_t *bs); +//!returns true if the bot is in observer mode +qboolean BotIsObserver(bot_state_t *bs); +//!returns true if the bot is in the intermission +qboolean BotIntermission(bot_state_t *bs); +//!returns true if the bot is in lava or slime +qboolean BotInLavaOrSlime(bot_state_t *bs); +//!returns true if the entity is dead +qboolean EntityIsDead(aas_entityinfo_t *entinfo); +//!returns true if the entity is invisible +qboolean EntityIsInvisible(aas_entityinfo_t *entinfo); +//!returns true if the entity is shooting +qboolean EntityIsShooting(aas_entityinfo_t *entinfo); +//!returns the name of the client +char *ClientName(int client, char *name, int size); +//!returns an simplyfied client name +char *EasyClientName(int client, char *name, int size); +//!returns the skin used by the client +char *ClientSkin(int client, char *skin, int size); +//!returns the aggression of the bot in the range [0, 100] +float BotAggression(bot_state_t *bs); +//!returns true if the bot wants to retreat +int BotWantsToRetreat(bot_state_t *bs); +//!returns true if the bot wants to chase +int BotWantsToChase(bot_state_t *bs); +//!returns true if the bot wants to help +int BotWantsToHelp(bot_state_t *bs); +//!returns true if the bot can and wants to rocketjump +int BotCanAndWantsToRocketJump(bot_state_t *bs); +//!returns true if the bot wants to and goes camping +int BotWantsToCamp(bot_state_t *bs); +//!the bot will perform attack movements +bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl); +//!returns true if the bot and the entity are in the same team +int BotSameTeam(bot_state_t *bs, int entnum); +//!returns true if teamplay is on +int TeamPlayIsOn(void); +//!returns visible team mate flag carrier if available +int BotTeamFlagCarrierVisible(bot_state_t *bs); +//!returns true and sets the .enemy field when an enemy is found +int BotFindEnemy(bot_state_t *bs, int curenemy); +//!returns a roam goal +void BotRoamGoal(bot_state_t *bs, vec3_t goal); +//!returns entity visibility in the range [0, 1] +float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent); +//!the bot will aim at the current enemy +void BotAimAtEnemy(bot_state_t *bs); +//!check if the bot should attack +void BotCheckAttack(bot_state_t *bs); +//!AI when the bot is blocked +void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate); +//!returns the CTF team the bot is in +int BotCTFTeam(bot_state_t *bs); +//!returns the flag the bot is carrying (CTFFLAG_?) +int BotCTFCarryingFlag(bot_state_t *bs); +//!set ctf goals (defend base, get enemy flag) during seek +void BotCTFSeekGoals(bot_state_t *bs); +//!set ctf goals (defend base, get enemy flag) during retreat +void BotCTFRetreatGoals(bot_state_t *bs); +//!create a new waypoint +bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum); +//!find a waypoint with the given name +bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name); +//!strstr but case insensitive +char *stristr(char *str, char *charset); +//!returns the number of the client with the given name +int ClientFromName(char *name); +// +int BotPointAreaNum(vec3_t origin); +// +void BotMapScripts(bot_state_t *bs); + +//ctf flags +#define CTF_FLAG_NONE 0 +#define CTF_FLAG_RED 1 +#define CTF_FLAG_BLUE 2 +//CTF skins +#define CTF_SKIN_REDTEAM "red" +#define CTF_SKIN_BLUETEAM "blue" +//CTF teams +#define CTF_TEAM_NONE 0 +#define CTF_TEAM_RED 1 +#define CTF_TEAM_BLUE 2 + +extern int gametype; //!allsolid = trace.allsolid; + bsptrace->startsolid = trace.startsolid; + bsptrace->fraction = trace.fraction; + VectorCopy(trace.endpos, bsptrace->endpos); + bsptrace->plane.dist = trace.plane.dist; + VectorCopy(trace.plane.normal, bsptrace->plane.normal); + bsptrace->plane.signbits = trace.plane.signbits; + bsptrace->plane.type = trace.plane.type; + bsptrace->surface.value = trace.surfaceFlags; + bsptrace->ent = trace.entityNum; + bsptrace->exp_dist = 0; + bsptrace->sidenum = 0; + bsptrace->contents = 0; +} + +/* +================== +BotAI_GetClientState +================== +*/ +int BotAI_GetClientState( int clientNum, playerState_t *state ) { + gentity_t *ent; + + ent = &g_entities[clientNum]; + if ( !ent->inuse ) { + return qfalse; + } + if ( !ent->client ) { + return qfalse; + } + + memcpy( state, &ent->client->ps, sizeof(playerState_t) ); + return qtrue; +} + +/* +================== +BotAI_GetEntityState +================== +*/ +int BotAI_GetEntityState( int entityNum, entityState_t *state ) { + gentity_t *ent; + + ent = &g_entities[entityNum]; + memset( state, 0, sizeof(entityState_t) ); + if (!ent->inuse) return qfalse; + if (!ent->r.linked) return qfalse; + if (ent->r.svFlags & SVF_NOCLIENT) return qfalse; + memcpy( state, &ent->s, sizeof(entityState_t) ); + return qtrue; +} + +/* +================== +BotAI_GetSnapshotEntity +================== +*/ +int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ) { + int entNum; + + entNum = trap_BotGetSnapshotEntity( clientNum, sequence ); + if ( entNum == -1 ) { + memset(state, 0, sizeof(entityState_t)); + return -1; + } + + BotAI_GetEntityState( entNum, state ); + + return sequence + 1; +} + +/* +================== +BotAI_BotInitialChat +================== +*/ +void QDECL BotAI_BotInitialChat( bot_state_t *bs, char *type, ... ) { + int i, mcontext; + va_list ap; + char *p; + char *vars[MAX_MATCHVARIABLES]; + + memset(vars, 0, sizeof(vars)); + va_start(ap, type); + p = va_arg(ap, char *); + for (i = 0; i < MAX_MATCHVARIABLES; i++) { + if( !p ) { + break; + } + vars[i] = p; + p = va_arg(ap, char *); + } + va_end(ap); + + mcontext = CONTEXT_NORMAL|CONTEXT_NEARBYITEM|CONTEXT_NAMES; + if (BotCTFTeam(bs) == CTF_TEAM_RED) mcontext |= CONTEXT_CTFREDTEAM; + else mcontext |= CONTEXT_CTFBLUETEAM; + + trap_BotInitialChat( bs->cs, type, mcontext, vars[0], vars[1], vars[2], vars[3], vars[4], vars[5], vars[6], vars[7] ); +} + + +/* +================== +BotTestSolid +================== +*/ +void BotTestSolid(vec3_t origin) { + int areanum; + + if( !bot_setupComplete ) { + return; + } + + trap_Cvar_Update(&bot_testsolid); + if (bot_testsolid.integer) { + if (!trap_AAS_Initialized()) return; + areanum = BotPointAreaNum(origin); + if (areanum) BotAI_Print(PRT_MESSAGE, "\remtpy area"); + else BotAI_Print(PRT_MESSAGE, "\r^1SOLID area"); + } +} + +/* +================== +BotReportStatus +================== +*/ +void BotReportStatus(bot_state_t *bs) { + char goalname[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + char *leader, *flagstatus; + // + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) == 0) leader = "L"; + else leader = " "; + if (BotCTFCarryingFlag(bs)) { + if (BotCTFTeam(bs) == TEAM_RED) flagstatus = S_COLOR_RED"F"; + else flagstatus = S_COLOR_BLUE"F"; + } + else { + flagstatus = " "; + } + switch(bs->ltgtype) { + case LTG_TEAMHELP: + { + EasyClientName(bs->teammate, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: helping %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_TEAMACCOMPANY: + { + EasyClientName(bs->teammate, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: accompanying %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_DEFENDKEYAREA: + { + trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: defending %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_GETITEM: + { + trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: getting item %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_KILL: + { + ClientName(bs->teamgoal.entitynum, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: killing %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_CAMP: + case LTG_CAMPORDER: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: camping\n", netname, leader, flagstatus); + break; + } + case LTG_PATROL: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: patrolling\n", netname, leader, flagstatus); + break; + } + case LTG_GETFLAG: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: capturing flag\n", netname, leader, flagstatus); + break; + } + case LTG_RUSHBASE: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: rushing base\n", netname, leader, flagstatus); + break; + } + case LTG_RETURNFLAG: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: returning flag\n", netname, leader, flagstatus); + break; + } + default: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: roaming\n", netname, leader, flagstatus); + break; + } + } +} + +/* +================== +BotTeamplayReport +================== +*/ +void BotTeamplayReport(void) { + int i; + char buf[MAX_INFO_STRING]; + + BotAI_Print(PRT_MESSAGE, S_COLOR_RED"RED\n"); + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + // + if ( !botstates[i] || !botstates[i]->inuse ) continue; + // + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_RED) { + BotReportStatus(botstates[i]); + } + } + BotAI_Print(PRT_MESSAGE, S_COLOR_BLUE"BLUE\n"); + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + // + if ( !botstates[i] || !botstates[i]->inuse ) continue; + // + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_BLUE) { + BotReportStatus(botstates[i]); + } + } +} + +/* +============== +BotInterbreedBots +============== +*/ +void BotInterbreedBots(void) { + float ranks[MAX_CLIENTS]; + int parent1, parent2, child; + int i; + + // get rankings for all the bots + for (i = 0; i < MAX_CLIENTS; i++) { + if ( botstates[i] && botstates[i]->inuse ) { + ranks[i] = botstates[i]->num_kills * 2 - botstates[i]->num_deaths; + } + else { + ranks[i] = -1; + } + } + + if (trap_GeneticParentsAndChildSelection(MAX_CLIENTS, ranks, &parent1, &parent2, &child)) { + trap_BotInterbreedGoalFuzzyLogic(botstates[parent1]->gs, botstates[parent2]->gs, botstates[child]->gs); + trap_BotMutateGoalFuzzyLogic(botstates[child]->gs, 1); + } + // reset the kills and deaths + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + botstates[i]->num_kills = 0; + botstates[i]->num_deaths = 0; + } + } +} + +/* +============== +BotWriteInterbreeded +============== +*/ +void BotWriteInterbreeded(char *filename) { + float rank, bestrank; + int i, bestbot; + + bestrank = 0; + bestbot = -1; + // get the best bot + for (i = 0; i < MAX_CLIENTS; i++) { + if ( botstates[i] && botstates[i]->inuse ) { + rank = botstates[i]->num_kills * 2 - botstates[i]->num_deaths; + } + else { + rank = -1; + } + if (rank > bestrank) { + bestrank = rank; + bestbot = i; + } + } + if (bestbot >= 0) { + //write out the new goal fuzzy logic + trap_BotSaveGoalFuzzyLogic(botstates[bestbot]->gs, filename); + } +} + +/* +============== +BotInterbreedEndMatch + +add link back into ExitLevel? +============== +*/ +void BotInterbreedEndMatch(void) { + + if (!bot_interbreed) return; + bot_interbreedmatchcount++; + if (bot_interbreedmatchcount >= bot_interbreedcycle.integer) { + bot_interbreedmatchcount = 0; + // + trap_Cvar_Update(&bot_interbreedwrite); + if (strlen(bot_interbreedwrite.string)) { + BotWriteInterbreeded(bot_interbreedwrite.string); + trap_Cvar_Set("bot_interbreedwrite", ""); + } + BotInterbreedBots(); + } +} + +/* +============== +BotInterbreeding +============== +*/ +void BotInterbreeding(void) { + int i; + + trap_Cvar_Update(&bot_interbreedchar); + if (!strlen(bot_interbreedchar.string)) return; + //make sure we are in tournament mode + if (gametype != GT_TOURNAMENT) { + trap_Cvar_Set("g_gametype", va("%d", GT_TOURNAMENT)); + ExitLevel(); + return; + } + //shutdown all the bots + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + BotAIShutdownClient(botstates[i]->client); + } + } + //make sure all item weight configs are reloaded and Not shared + trap_BotLibVarSet("bot_reloadcharacters", "1"); + //add a number of bots using the desired bot character + for (i = 0; i < bot_interbreedbots.integer; i++) { + trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s 4 free %i %s%d\n", + bot_interbreedchar.string, i * 50, bot_interbreedchar.string, i) ); + } + // + trap_Cvar_Set("bot_interbreedchar", ""); + bot_interbreed = qtrue; +} + +/* +============== +BotEntityInfo +============== +*/ +void BotEntityInfo(int entnum, aas_entityinfo_t *info) { + trap_AAS_EntityInfo(entnum, info); +} + +/* +============== +NumBots +============== +*/ +int NumBots(void) { + return numbots; +} + +/* +============== +BotTeamLeader +============== +*/ +int BotTeamLeader(bot_state_t *bs) { + int leader; + + leader = ClientFromName(bs->teamleader); + if (leader < 0) return qfalse; + if (!botstates[leader] || !botstates[leader]->inuse) return qfalse; + return qtrue; +} + +/* +============== +AngleDifference +============== +*/ +float AngleDifference(float ang1, float ang2) { + float diff; + + diff = ang1 - ang2; + if (ang1 > ang2) { + if (diff > 180.0) diff -= 360.0; + } + else { + if (diff < -180.0) diff += 360.0; + } + return diff; +} + +/* +============== +BotChangeViewAngle +============== +*/ +float BotChangeViewAngle(float angle, float ideal_angle, float speed) { + float move; + + angle = AngleMod(angle); + ideal_angle = AngleMod(ideal_angle); + if (angle == ideal_angle) return angle; + move = ideal_angle - angle; + if (ideal_angle > angle) { + if (move > 180.0) move -= 360.0; + } + else { + if (move < -180.0) move += 360.0; + } + if (move > 0) { + if (move > speed) move = speed; + } + else { + if (move < -speed) move = -speed; + } + return AngleMod(angle + move); +} + +/* +============== +BotChangeViewAngles +============== +*/ +void BotChangeViewAngles(bot_state_t *bs, float thinktime) { + float diff, factor, maxchange, anglespeed; + int i; + + if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360; + + factor = 1; + maxchange = 1800; + + maxchange *= thinktime; + for (i = 0; i < 2; i++) + { + //smooth slowdown view model + diff = abs(AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i])); + anglespeed = diff * factor; + if (anglespeed > maxchange) + { + anglespeed = maxchange; + } + bs->viewangles[i] = BotChangeViewAngle(bs->viewangles[i], bs->ideal_viewangles[i], anglespeed); + //BotAI_Print(PRT_MESSAGE, "ideal_angles %f %f\n", bs->ideal_viewangles[0], bs->ideal_viewangles[1], bs->ideal_viewangles[2]);` + //bs->viewangles[i] = bs->ideal_viewangles[i]; + } + //bs->viewangles[PITCH] = 0; + if (bs->viewangles[PITCH] > 180) bs->viewangles[PITCH] -= 360; + //elementary action: view + trap_EA_View(bs->client, bs->viewangles); +} + +/* +============== +BotInputToUserCommand +============== +*/ +void BotInputToUserCommand(bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3], int time) { + vec3_t angles, forward, right; + short temp; + int j; + + //clear the whole structure + memset(ucmd, 0, sizeof(usercmd_t)); + // + //Com_Printf("dir = %f %f %f speed = %f\n", bi->dir[0], bi->dir[1], bi->dir[2], bi->speed); + //the duration for the user command in milli seconds + ucmd->serverTime = time; + // + if (bi->actionflags & ACTION_DELAYEDJUMP) { + bi->actionflags |= ACTION_JUMP; + bi->actionflags &= ~ACTION_DELAYEDJUMP; + } + //set the buttons + if (bi->actionflags & ACTION_RESPAWN) ucmd->buttons = BUTTON_ATTACK; + if (bi->actionflags & ACTION_ATTACK) ucmd->buttons |= BUTTON_ATTACK; + if (bi->actionflags & ACTION_TALK) ucmd->buttons |= BUTTON_TALK; + if (bi->actionflags & ACTION_GESTURE) ucmd->buttons |= BUTTON_GESTURE; + if (bi->actionflags & ACTION_USE) ucmd->buttons |= BUTTON_USE_HOLDABLE; + if (bi->actionflags & ACTION_WALK) ucmd->buttons |= BUTTON_WALKING; + if (bi->actionflags & ACTION_ALT_ATTACK) + { + ucmd->buttons |= BUTTON_ALT_ATTACK; + } + + ucmd->weapon = bi->weapon; + //set the view angles + //NOTE: the ucmd->angles are the angles WITHOUT the delta angles + ucmd->angles[PITCH] = ANGLE2SHORT(bi->viewangles[PITCH]); + ucmd->angles[YAW] = ANGLE2SHORT(bi->viewangles[YAW]); + ucmd->angles[ROLL] = ANGLE2SHORT(bi->viewangles[ROLL]); + //subtract the delta angles + for (j = 0; j < 3; j++) { + temp = ucmd->angles[j] - delta_angles[j]; + /*NOTE: disabled because temp should be mod first + if ( j == PITCH ) { + // don't let the player look up or down more than 90 degrees + if ( temp > 16000 ) temp = 16000; + else if ( temp < -16000 ) temp = -16000; + } + */ + ucmd->angles[j] = temp; + } + //NOTE: movement is relative to the REAL view angles + //get the horizontal forward and right vector + //get the pitch in the range [-180, 180] + if (bi->dir[2]) angles[PITCH] = bi->viewangles[PITCH]; + else angles[PITCH] = 0; + angles[YAW] = bi->viewangles[YAW]; + angles[ROLL] = 0; + AngleVectors(angles, forward, right, NULL); + //bot input speed is in the range [0, 400] + bi->speed = bi->speed * 127 / 400; + //set the view independent movement + ucmd->forwardmove = DotProduct(forward, bi->dir) * bi->speed; + ucmd->rightmove = DotProduct(right, bi->dir) * bi->speed; + ucmd->upmove = abs(forward[2]) * bi->dir[2] * bi->speed; + //normal keyboard movement + if (bi->actionflags & ACTION_MOVEFORWARD) ucmd->forwardmove += 127; + if (bi->actionflags & ACTION_MOVEBACK) ucmd->forwardmove -= 127; + if (bi->actionflags & ACTION_MOVELEFT) ucmd->rightmove -= 127; + if (bi->actionflags & ACTION_MOVERIGHT) ucmd->rightmove += 127; + //jump/moveup + if (bi->actionflags & ACTION_JUMP) ucmd->upmove += 127; + //crouch/movedown + if (bi->actionflags & ACTION_CROUCH) ucmd->upmove -= 127; + // + //Com_Printf("forward = %d right = %d up = %d\n", ucmd.forwardmove, ucmd.rightmove, ucmd.upmove); + //Com_Printf("ucmd->serverTime = %d\n", ucmd->serverTime); +} + +/* +============== +BotUpdateInput +============== +*/ +void BotUpdateInput(bot_state_t *bs, int time, 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 (bs->lastucmd.buttons & BUTTON_ATTACK) bi.actionflags &= ~(ACTION_RESPAWN|ACTION_ATTACK); + } + //convert the bot input to a usercmd + BotInputToUserCommand(&bi, &bs->lastucmd, bs->cur_ps.delta_angles, time); + //subtract the delta angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } +} + +/* +============== +BotAIRegularUpdate +============== +*/ +void BotAIRegularUpdate(void) { + if (regularupdate_time < trap_AAS_Time()) { + trap_BotUpdateEntityItems(); + regularupdate_time = trap_AAS_Time() + 0.3; + } +} + +/* +============== +BotAI +============== +*/ +int BotAI(int client, float thinktime) { + bot_state_t *bs; + char buf[1024], *args; + int j; + + 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 console messages + while( trap_BotGetConsoleMessage(client, buf, sizeof(buf)) ) { + //have buf point to the command and args to the command arguments + args = strchr( buf, ' '); + if (!args) continue; + *args++ = '\0'; + + //remove color espace sequences from the arguments + Q_CleanStr( args ); + + if (!Q_stricmp(buf, "cp ")) + { /*CenterPrintf*/ } + else if (!Q_stricmp(buf, "cs")) + { /*ConfigStringModified*/ } + else if (!Q_stricmp(buf, "print")) { + //remove first and last quote from the chat message + memmove(args, args+1, strlen(args)); + args[strlen(args)-1] = '\0'; + trap_BotQueueConsoleMessage(bs->cs, CMS_NORMAL, args); + } + else if (!Q_stricmp(buf, "chat")) { + //remove first and last quote from the chat message + memmove(args, args+1, strlen(args)); + args[strlen(args)-1] = '\0'; + trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, args); + } + else if (!Q_stricmp(buf, "tchat")) { + //remove first and last quote from the chat message + memmove(args, args+1, strlen(args)); + args[strlen(args)-1] = '\0'; + trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, args); + } + else if (!Q_stricmp(buf, "scores")) + { /*FIXME: parse scores?*/ } + else if (!Q_stricmp(buf, "clientLevelShot")) + { /*ignore*/ } + } + //add the delta angles to the bot's current view angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } + //increase the local time of the bot + bs->ltime += thinktime; + // + bs->thinktime = thinktime; + //origin of the bot + VectorCopy(bs->cur_ps.origin, bs->origin); + //eye coordinates of the bot + VectorCopy(bs->cur_ps.origin, bs->eye); + bs->eye[2] += bs->cur_ps.viewheight; + //get the area the bot is in + bs->areanum = BotPointAreaNum(bs->origin); + //the real AI + BotDeathmatchAI(bs, thinktime); + //set the weapon selection every AI frame + trap_EA_SelectWeapon(bs->client, bs->weaponnum); + //subtract the delta angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } + //everything was ok + return 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_thinktime.integer * botnum / numbots; + botnum++; + } +} + +/* +============== +BotAISetupClient +============== +*/ +int BotAISetupClient(int client, struct bot_settings_s *settings) { + char filename[AI_MAX_PATH], name[AI_MAX_PATH], gender[AI_MAX_PATH]; + bot_state_t *bs; + int errnum; + + if (!botstates[client]) botstates[client] = G_Alloc(sizeof(bot_state_t)); + bs = botstates[client]; + + if(!bs) return qfalse; + + if (bs && bs->inuse) { + BotAI_Print(PRT_FATAL, "BotAISetupClient: client %d already setup\n", client); + return qfalse; + } + + if (!trap_AAS_Initialized()) { + BotAI_Print(PRT_FATAL, "AAS not initialized\n"); + return qfalse; + } + + //load the bot character + bs->character = trap_BotLoadCharacter(settings->characterfile, settings->skill); + if (!bs->character) { + BotAI_Print(PRT_FATAL, "couldn't load skill %d from %s\n", settings->skill, settings->characterfile); + return qfalse; + } + //copy the settings + memcpy(&bs->settings, settings, sizeof(bot_settings_t)); + //allocate a goal state + bs->gs = trap_BotAllocGoalState(client); + //load the item weights + trap_Characteristic_String(bs->character, CHARACTERISTIC_ITEMWEIGHTS, filename, AI_MAX_PATH); + errnum = trap_BotLoadItemWeights(bs->gs, filename); + if (errnum != BLERR_NOERROR) { + trap_BotFreeGoalState(bs->gs); + return qfalse; + } + //allocate a weapon state + bs->ws = trap_BotAllocWeaponState(); + //load the weapon weights + trap_Characteristic_String(bs->character, CHARACTERISTIC_WEAPONWEIGHTS, filename, AI_MAX_PATH); + errnum = trap_BotLoadWeaponWeights(bs->ws, filename); + if (errnum != BLERR_NOERROR) { + trap_BotFreeGoalState(bs->gs); + trap_BotFreeWeaponState(bs->ws); + return qfalse; + } + //allocate a chat state + bs->cs = trap_BotAllocChatState(); + //load the chat file + trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_FILE, filename, AI_MAX_PATH); + trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_NAME, name, AI_MAX_PATH); + errnum = trap_BotLoadChatFile(bs->cs, filename, name); + if (errnum != BLERR_NOERROR) { + trap_BotFreeChatState(bs->cs); + trap_BotFreeGoalState(bs->gs); + trap_BotFreeWeaponState(bs->ws); + return qfalse; + } + //get the gender characteristic + trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, AI_MAX_PATH); + //set the chat gender + if (*gender == 'f' || *gender == 'F') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE); + else if (*gender == 'm' || *gender == 'M') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE); + else trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS); + + bs->inuse = qtrue; + bs->client = client; + bs->entitynum = client; + bs->setupcount = 4; + bs->entergame_time = trap_AAS_Time(); + bs->ms = trap_BotAllocMoveState(); + bs->walker = 0; + numbots++; + + if (trap_Cvar_VariableIntegerValue("bot_testichat")) { + trap_BotLibVarSet("bot_testichat", "1"); + BotChatTest(bs); + } + //NOTE: reschedule the bot thinking + BotScheduleBotThink(); + //if interbreeding start with a mutation + if (bot_interbreed) { + trap_BotMutateGoalFuzzyLogic(bs->gs, 1); + } + //bot has been setup succesfully + return qtrue; +} + +/* +============== +BotAIShutdownClient +============== +*/ +int BotAIShutdownClient(int client) { + bot_state_t *bs; + + bs = botstates[client]; + if (!bs || !bs->inuse) { + //BotAI_Print(PRT_ERROR, "BotAIShutdownClient: client %d already shutdown\n", client); + return qfalse; + } + + if (BotChat_ExitGame(bs)) { + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + + trap_BotFreeMoveState(bs->ms); + //free the goal state` + trap_BotFreeGoalState(bs->gs); + //free the chat file + trap_BotFreeChatState(bs->cs); + //free the weapon weights + trap_BotFreeWeaponState(bs->ws); + //free the bot character + trap_BotFreeCharacter(bs->character); + // + BotFreeWaypoints(bs->checkpoints); + BotFreeWaypoints(bs->patrolpoints); + //clear the bot state + memset(bs, 0, sizeof(bot_state_t)); + //set the inuse flag to qfalse + bs->inuse = qfalse; + //there's one bot less + numbots--; + //everything went ok + return 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, chatstate, weaponstate; + bot_settings_t settings; + int character; + playerState_t ps; //current player state + float entergame_time; + + //save some things that should not be reset here + memcpy(&settings, &bs->settings, sizeof(bot_settings_t)); + memcpy(&ps, &bs->cur_ps, sizeof(playerState_t)); + inuse = bs->inuse; + client = bs->client; + entitynum = bs->entitynum; + character = bs->character; + movestate = bs->ms; + goalstate = bs->gs; + chatstate = bs->cs; + weaponstate = bs->ws; + entergame_time = bs->entergame_time; + //free checkpoints and patrol points + BotFreeWaypoints(bs->checkpoints); + BotFreeWaypoints(bs->patrolpoints); + //reset the whole state + memset(bs, 0, sizeof(bot_state_t)); + //copy back some state stuff that should not be reset + bs->ms = movestate; + bs->gs = goalstate; + bs->cs = chatstate; + bs->ws = weaponstate; + memcpy(&bs->cur_ps, &ps, sizeof(playerState_t)); + memcpy(&bs->settings, &settings, sizeof(bot_settings_t)); + bs->inuse = inuse; + bs->client = client; + bs->entitynum = entitynum; + bs->character = character; + bs->entergame_time = entergame_time; + //reset several states + if (bs->ms) trap_BotResetMoveState(bs->ms); + if (bs->gs) trap_BotResetGoalState(bs->gs); + if (bs->ws) trap_BotResetWeaponState(bs->ws); + if (bs->gs) trap_BotResetAvoidGoals(bs->gs); + if (bs->ms) trap_BotResetAvoidReach(bs->ms); +} + +/* +============== +BotAILoadMap +============== +*/ +int BotAILoadMap( int restart ) { + int i; + vmCvar_t mapname; + + if (!restart) { + trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); + trap_BotLibLoadMap( mapname.string ); + } + + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + BotResetState( botstates[i] ); + botstates[i]->setupcount = 4; + } + } + + BotSetupDeathmatchAI(); + + return qtrue; +} + +/* +================== +BotAIStartFrame +================== +*/ +int BotAIStartFrame(int time) { + int i; + gentity_t *ent; + bot_entitystate_t state; + int elapsed_time, thinktime; + static int local_time; + static int botlib_residual; + static int lastbotthink_time; + + G_CheckBotSpawn(); + + trap_Cvar_Update(&bot_rocketjump); + trap_Cvar_Update(&bot_grapple); + trap_Cvar_Update(&bot_fastchat); + trap_Cvar_Update(&bot_nochat); + trap_Cvar_Update(&bot_testrchat); + trap_Cvar_Update(&bot_thinktime); + trap_Cvar_Update(&bot_memorydump); + trap_Cvar_Update(&bot_pause); + trap_Cvar_Update(&bot_report); + + if (bot_report.integer) { + BotTeamplayReport(); + trap_Cvar_Set("bot_report", "0"); + } + + if (bot_pause.integer) { + // 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; + } + botstates[i]->lastucmd.forwardmove = 0; + botstates[i]->lastucmd.rightmove = 0; + botstates[i]->lastucmd.upmove = 0; + botstates[i]->lastucmd.buttons = 0; + botstates[i]->lastucmd.serverTime = time; + trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd); + } + return qtrue; + } + + if (bot_memorydump.integer) { + trap_BotLibVarSet("memorydump", "1"); + trap_Cvar_Set("bot_memorydump", "0"); + } + //check if bot interbreeding is activated + BotInterbreeding(); + //cap the bot think time + if (bot_thinktime.integer > 200) { + trap_Cvar_Set("bot_thinktime", "200"); + } + //if the bot think time changed we should reschedule the bots + if (bot_thinktime.integer != lastbotthink_time) { + lastbotthink_time = bot_thinktime.integer; + BotScheduleBotThink(); + } + + elapsed_time = time - local_time; + local_time = time; + + botlib_residual += elapsed_time; + + if (elapsed_time > bot_thinktime.integer) thinktime = elapsed_time; + else thinktime = bot_thinktime.integer; + + // update the bot library + if ( botlib_residual >= thinktime ) { + botlib_residual -= thinktime; + + trap_BotLibStartFrame((float) time / 1000); + + if (!trap_AAS_Initialized()) return qfalse; + + //update entities in the botlib + for (i = 0; i < MAX_GENTITIES; i++) { + ent = &g_entities[i]; + if (!ent->inuse) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } + if (!ent->r.linked) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } + if (ent->r.svFlags & SVF_NOCLIENT) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } + // do not update missiles + if (ent->s.eType == ET_MISSILE) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } + // do not update event only entities + if (ent->s.eType > ET_EVENTS) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } + // + memset(&state, 0, sizeof(bot_entitystate_t)); + // + VectorCopy(ent->r.currentOrigin, state.origin); + if (i < MAX_CLIENTS) { + VectorCopy(ent->s.apos.trBase, state.angles); + } else { + VectorCopy(ent->r.currentAngles, state.angles); + } + VectorCopy(ent->s.origin2, state.old_origin); + VectorCopy(ent->r.mins, state.mins); + VectorCopy(ent->r.maxs, state.maxs); + state.type = ent->s.eType; + state.flags = ent->s.eFlags; + if (ent->r.bmodel) state.solid = SOLID_BSP; + else state.solid = SOLID_BBOX; + state.groundent = ent->s.groundEntityNum; + state.modelindex = ent->s.modelindex; + state.modelindex2 = ent->s.modelindex2; + state.frame = ent->s.frame; + state.event = ent->s.event; + state.eventParm = ent->s.eventParm; + state.powerups = ent->s.powerups; + state.legsAnim = ent->s.legsAnim; + state.torsoAnim = ent->s.torsoAnim; + state.weapon = ent->s.weapon; + // + trap_BotLibUpdateEntity(i, &state); + } + + BotAIRegularUpdate(); + } + + // 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 (!trap_AAS_Initialized()) return qfalse; + + 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; +} + +/* +============== +BotInitLibrary +============== +*/ +int BotInitLibrary(void) { + int gt; + char buf[144]; + + //set the maxclients and maxentities library variables before calling BotSetupLibrary + trap_Cvar_VariableStringBuffer("sv_maxclients", buf, sizeof(buf)); + if (!strlen(buf)) strcpy(buf, "8"); + trap_BotLibVarSet("maxclients", buf); + Com_sprintf(buf, sizeof(buf), "%d", MAX_GENTITIES); + trap_BotLibVarSet("maxentities", buf); + //bsp checksum + trap_Cvar_VariableStringBuffer("sv_mapChecksum", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("sv_mapChecksum", buf); + //maximum number of aas links + trap_Cvar_VariableStringBuffer("max_aaslinks", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("max_aaslinks", buf); + //maximum number of items in a level + trap_Cvar_VariableStringBuffer("max_levelitems", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("max_levelitems", buf); + //game type + gt = trap_Cvar_VariableIntegerValue("g_gametype"); + if( gt == GT_SINGLE_PLAYER ) { + gt = AIGT_SINGLE_PLAYER; + } + else if( gt >= GT_TEAM ) { + gt = AIGT_TEAM; + } + else { + gt = AIGT_OTHER; + } + trap_BotLibVarSet("ai_gametype", va("%i", gt)); + //bot developer mode and log file + trap_Cvar_VariableStringBuffer("bot_developer", buf, sizeof(buf)); + if (!strlen(buf)) strcpy(buf, "0"); + trap_BotLibVarSet("bot_developer", buf); + trap_BotLibVarSet("log", buf); + //no chatting + trap_Cvar_VariableStringBuffer("bot_nochat", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("nochat", "0"); + //visualize jump pads + trap_Cvar_VariableStringBuffer("bot_visualizejumppads", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("bot_visualizejumppads", buf); + //forced clustering calculations + trap_Cvar_VariableStringBuffer("bot_forceclustering", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("forceclustering", buf); + //forced reachability calculations + trap_Cvar_VariableStringBuffer("bot_forcereachability", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("forcereachability", buf); + //force writing of AAS to file + trap_Cvar_VariableStringBuffer("bot_forcewrite", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("forcewrite", buf); + //no AAS optimization + trap_Cvar_VariableStringBuffer("bot_aasoptimize", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("aasoptimize", buf); + //reload instead of cache bot character files + trap_Cvar_VariableStringBuffer("bot_reloadcharacters", buf, sizeof(buf)); + if (!strlen(buf)) strcpy(buf, "0"); + trap_BotLibVarSet("bot_reloadcharacters", buf); + //base directory + trap_Cvar_VariableStringBuffer("fs_basepath", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("basedir", buf); + //game directory + trap_Cvar_VariableStringBuffer("fs_game", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("gamedir", buf); + //cd directory + trap_Cvar_VariableStringBuffer("fs_cdpath", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("cddir", buf); + //setup the bot library + return trap_BotLibSetup(); +} + +/* +============== +BotAISetup +============== +*/ +int BotAISetup( int restart ) { + int errnum; + +#ifdef RANDOMIZE + srand((unsigned)time(NULL)); +#endif //RANDOMIZE + + bot_setupComplete = qtrue; + + trap_Cvar_Register(&bot_thinktime, "bot_thinktime", "100", CVAR_CHEAT); + trap_Cvar_Register(&bot_memorydump, "bot_memorydump", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_pause, "bot_pause", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_report, "bot_report", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_testsolid, "bot_testsolid", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_interbreedchar, "bot_interbreedchar", "", 0); + trap_Cvar_Register(&bot_interbreedbots, "bot_interbreedbots", "10", 0); + trap_Cvar_Register(&bot_interbreedcycle, "bot_interbreedcycle", "20", 0); + trap_Cvar_Register(&bot_interbreedwrite, "bot_interbreedwrite", "", 0); + + //if the game is restarted for a tournament + if (restart) { + return qtrue; + } + + //initialize the bot states + memset( botstates, 0, sizeof(botstates) ); + + errnum = BotInitLibrary(); + if (errnum != BLERR_NOERROR) return qfalse; + 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); + } + } + //don't shutdown the bot library + } + else { + trap_BotLibShutdown(); + } + return qtrue; +} + diff --git a/game/ai_main.h b/game/ai_main.h new file mode 100644 index 0000000..f682376 --- /dev/null +++ b/game/ai_main.h @@ -0,0 +1,226 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_main.h + * + * desc: Quake3 bot AI + * + * $Archive: /StarTrek/Code-DM/game/ai_main.h $ + * $Author: Jmonroe $ + * $Revision: 1 $ + * $Modtime: 1/21/00 10:12p $ + * $Date: 1/25/00 6:26p $ + * + *****************************************************************************/ + +//#define DEBUG +#define CTF + +#define MAX_ITEMS 256 +//bot flags +#define BFL_STRAFERIGHT 1 //!teamleader)) return qfalse; + if (ClientFromName(bs->teamleader) == -1) return qfalse; + return qtrue; +} + +/* +================== +BotNumTeamMates +================== +*/ +int BotNumTeamMates(bot_state_t *bs) { + int i, numplayers; + char buf[MAX_INFO_STRING]; + static int maxclis; + + if (!maxclis) + maxclis = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + numplayers = 0; + for (i = 0; i < maxclis && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + if (BotSameTeam(bs, i)) { + numplayers++; + } + } + return numplayers; +} + +/* +================== +BotClientTravelTimeToGoal +================== +*/ +int BotClientTravelTimeToGoal(int client, bot_goal_t *goal) { + playerState_t ps; + int areanum; + + BotAI_GetClientState(client, &ps); + areanum = BotPointAreaNum(ps.origin); + if (!areanum) return 1; + return trap_AAS_AreaTravelTimeToGoalArea(areanum, ps.origin, goal->areanum, TFL_DEFAULT); +} + +/* +================== +BotSortTeamMatesByBaseTravelTime +================== +*/ +int BotSortTeamMatesByBaseTravelTime(bot_state_t *bs, int *teammates, int maxteammates) { + + int i, j, k, numteammates, traveltime; + char buf[MAX_INFO_STRING]; + static int maxclients; + int traveltimes[MAX_CLIENTS]; + bot_goal_t *goal; + + if (BotCTFTeam(bs) == CTF_TEAM_RED) goal = &ctf_redflag; + else goal = &ctf_blueflag; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + numteammates = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + if (BotSameTeam(bs, i)) { + // + traveltime = BotClientTravelTimeToGoal(i, goal); + // + for (j = 0; j < numteammates; j++) { + if (traveltime < traveltimes[j]) { + for (k = numteammates; k > j; k--) { + traveltimes[k] = traveltimes[k-1]; + teammates[k] = teammates[k-1]; + } + traveltimes[j] = traveltime; + teammates[j] = i; + break; + } + } + if (j >= numteammates) { + traveltimes[j] = traveltime; + teammates[j] = i; + } + numteammates++; + if (numteammates >= maxteammates) break; + } + } + return numteammates; +} + +/* +================== +BotGetTeamMateCTFPreference +================== +*/ +void BotSetTeamMateCTFPreference(bot_state_t *bs, int teammate, int preference) { + char teammatename[MAX_NETNAME]; + + ctftaskpreferences[teammate].preference = preference; + ClientName(teammate, teammatename, sizeof(teammatename)); + strcpy(ctftaskpreferences[teammate].name, teammatename); +} + +/* +================== +BotGetTeamMateCTFPreference +================== +*/ +int BotGetTeamMateCTFPreference(bot_state_t *bs, int teammate) { + char teammatename[MAX_NETNAME]; + + if (!ctftaskpreferences[teammate].preference) return 0; + ClientName(teammate, teammatename, sizeof(teammatename)); + if (Q_stricmp(teammatename, ctftaskpreferences[teammate].name)) return 0; + return ctftaskpreferences[teammate].preference; +} + +/* +================== +BotSortTeamMatesByCTFPreference +================== +*/ +int BotSortTeamMatesByCTFPreference(bot_state_t *bs, int *teammates, int numteammates) { + int defenders[MAX_CLIENTS], numdefenders; + int attackers[MAX_CLIENTS], numattackers; + int roamers[MAX_CLIENTS], numroamers; + int i, preference; + + numdefenders = numattackers = numroamers = 0; + for (i = 0; i < numteammates; i++) { + preference = BotGetTeamMateCTFPreference(bs, teammates[i]); + if (preference & CTFTP_DEFENDER) { + defenders[numdefenders++] = teammates[i]; + } + else if (preference & CTFTP_ATTACKER) { + attackers[numattackers++] = teammates[i]; + } + else { + roamers[numroamers++] = teammates[i]; + } + } + numteammates = 0; + //defenders at the front of the list + memcpy(&teammates[numteammates], defenders, numdefenders); + numteammates += numdefenders; + //roamers in the middle + memcpy(&teammates[numteammates], roamers, numroamers); + numteammates += numroamers; + //attacker in the back of the list + memcpy(&teammates[numteammates], attackers, numattackers); + numteammates += numattackers; + + return numteammates; +} + +/* +================== +BotSayTeamOrders +================== +*/ +void BotSayTeamOrder(bot_state_t *bs, int toclient) { + char teamchat[MAX_MESSAGE_SIZE]; + char buf[MAX_MESSAGE_SIZE]; + char name[MAX_NETNAME]; + + //if the bot is talking to itself + if (bs->client == toclient) { + //don't show the message just put it in the console message queue + trap_BotGetChatMessage(bs->cs, buf, sizeof(buf)); + ClientName(bs->client, name, sizeof(name)); + Com_sprintf(teamchat, sizeof(teamchat), "(%s): %s", name, buf); + trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, teamchat); + } + else { + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + } +} + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_BothFlagsNotAtBase(bot_state_t *bs) { + int numteammates, defenders, attackers, i, other; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME], carriername[MAX_NETNAME]; + + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + BotSortTeamMatesByCTFPreference(bs, teammates, numteammates); + //different orders based on the number of team mates + switch(bs->numteammates) { + case 1: break; + case 2: + { + //tell the one not carrying the flag to attack the enemy base + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, other); + break; + } + case 3: + { + //tell the one closest to the base not carrying the flag to accompany the flag carrier + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + } + BotSayTeamOrder(bs, other); + //tell the one furthest from the the base not carrying the flag to get the enemy flag + if (teammates[2] != bs->flagcarrier) other = teammates[2]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, other); + break; + } + default: + { + defenders = (int) (float) numteammates * 0.4 + 0.5; + if (defenders > 1) defenders = 1; + attackers = (int) (float) numteammates * 0.5 + 0.5; + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + for (i = 0; i < defenders; i++) { + // + if (teammates[i] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[i], name, sizeof(name)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + } + BotSayTeamOrder(bs, teammates[i]); + } + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + } + // + break; + } + } +} + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_FlagNotAtBase(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + BotSortTeamMatesByCTFPreference(bs, teammates, numteammates); + //agressive + //different orders based on the number of team mates + switch(bs->numteammates) + { + case 1: break; + case 2: + { + //both will go for the enemy flag + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + // + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + break; + } + case 3: + { + //everyone go for the flag + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + // + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + break; + } + default: + { + //keep some people near the base for when the flag is returned + defenders = (int) (float) numteammates * 0.2 + 0.5; + if (defenders > 1) defenders = 1; + attackers = (int) (float) numteammates * 0.7 + 0.5; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + } + // + break; + } + } +} + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_EnemyFlagNotAtBase(bot_state_t *bs) { + int numteammates, defenders, attackers, i, other; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME], carriername[MAX_NETNAME]; + + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + BotSortTeamMatesByCTFPreference(bs, teammates, numteammates); + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //tell the one not carrying the flag to defend the base + if (teammates[0] == bs->flagcarrier) other = teammates[1]; + else other = teammates[0]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + break; + } + case 3: + { + //tell the one closest to the base not carrying the flag to defend the base + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + //tell the one furthest from the base not carrying the flag to accompany the flag carrier + if (teammates[2] != bs->flagcarrier) other = teammates[2]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + } + BotSayTeamOrder(bs, other); + break; + } + default: + { + //40% will defend the base + defenders = (int) (float) numteammates * 0.4 + 0.5; + if (defenders > 1) defenders = 1; + //50% accompanies the flag carrier + attackers = (int) (float) numteammates * 0.5 + 0.5; + for (i = 0; i < defenders; i++) { + // + if (teammates[i] == bs->flagcarrier) { + continue; + } + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + } + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + } + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + } + // + break; + } + } +} + + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_BothFlagsAtBase(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByCTFPreference(bs, teammates, numteammates); + //agressive + //different orders based on the number of team mates + switch(numteammates) + { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + //the others should go for the enemy flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + break; + } + default: + { + defenders = (int) (float) numteammates * 0.4 + 0.5; + if (defenders > 1) defenders = 1; + attackers = (int) (float) numteammates * 0.5 + 0.5; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + } + // + break; + } + } +} + + +/* +================== +BotTeamOrders +================== +*/ +void BotTeamOrders(bot_state_t *bs) { + //no teamplay orders at this time +} + + +/* +================== +BotTeamAI +================== +*/ +void BotTeamAI(bot_state_t *bs) { + int numteammates, flagstatus; + char netname[MAX_NETNAME]; + + if(!bs) return; + + // + if (gametype != GT_TEAM && gametype != GT_CTF) return; + //make sure we've got a valid team leader + if (!BotValidTeamLeader(bs)) { + // + if (!bs->askteamleader_time && !bs->becometeamleader_time) { + if (bs->entergame_time + 10 > trap_AAS_Time()) { + bs->askteamleader_time = trap_AAS_Time() + 5 + random() * 10; + } + else { + bs->becometeamleader_time = trap_AAS_Time() + 5 + random() * 10; + } + } + if (bs->askteamleader_time && bs->askteamleader_time < trap_AAS_Time()) { + //if asked for a team leader and no repsonse + BotAI_BotInitialChat(bs, "whoisteamleader", NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + bs->askteamleader_time = 0; + bs->becometeamleader_time = trap_AAS_Time() + 15 + random() * 10; + } + if (bs->becometeamleader_time && bs->becometeamleader_time < trap_AAS_Time()) { + BotAI_BotInitialChat(bs, "iamteamleader", NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + ClientName(bs->client, netname, sizeof(netname)); + strncpy(bs->teamleader, netname, sizeof(bs->teamleader)); + bs->teamleader[sizeof(bs->teamleader)-1] = '\0'; + bs->becometeamleader_time = 0; + } + return; + } + bs->askteamleader_time = 0; + bs->becometeamleader_time = 0; + + //return if this bot is NOT the team leader + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) != 0) return; + // + //if the game starts OR a new player comes onto the team OR a player leaves the team + // + numteammates = BotNumTeamMates(bs); + //give orders + switch(gametype) { + case GT_TEAM: + { + if (bs->numteammates != numteammates || bs->forceorders) { + bs->teamgiveorders_time = trap_AAS_Time(); + bs->numteammates = numteammates; + bs->forceorders = qfalse; + } + //if it's time to give orders + if (bs->teamgiveorders_time < trap_AAS_Time() - 5) { + BotTeamOrders(bs); + // + bs->teamgiveorders_time = 0; + } + break; + } + case GT_CTF: + { + //if the number of team mates changed or the flag status changed + //or someone wants to know what to do + if (bs->numteammates != numteammates || bs->flagstatuschanged || bs->forceorders) { + bs->teamgiveorders_time = trap_AAS_Time(); + bs->numteammates = numteammates; + bs->flagstatuschanged = qfalse; + bs->forceorders = qfalse; + } + //if there were no flag captures the last 3 minutes + if (bs->lastflagcapture_time < trap_AAS_Time() - 240) { + bs->lastflagcapture_time = trap_AAS_Time(); + //randomly change the CTF strategy + if (random() < 0.4) { + bs->ctfstrategy ^= CTFS_PASSIVE; + bs->teamgiveorders_time = trap_AAS_Time(); + } + } + //if it's time to give orders + if (bs->teamgiveorders_time && bs->teamgiveorders_time < trap_AAS_Time() - 3) { + // + if (BotCTFTeam(bs) == CTF_TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus; + else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus; + // + switch(flagstatus) { + case 0: BotCTFOrders_BothFlagsAtBase(bs); break; + case 1: BotCTFOrders_EnemyFlagNotAtBase(bs); break; + case 2: BotCTFOrders_FlagNotAtBase(bs); break; + case 3: BotCTFOrders_BothFlagsNotAtBase(bs); break; + } + // + bs->teamgiveorders_time = 0; + } + break; + } + } +} + diff --git a/game/ai_team.h b/game/ai_team.h new file mode 100644 index 0000000..519642e --- /dev/null +++ b/game/ai_team.h @@ -0,0 +1,19 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_team.h + * + * desc: Quake3 bot AI + * + * $Archive: /StarTrek/Code-DM/game/ai_team.h $ + * $Author: Jmonroe $ + * $Revision: 1 $ + * $Modtime: 1/21/00 10:12p $ + * $Date: 1/25/00 6:26p $ + * + *****************************************************************************/ + +void BotTeamAI(bot_state_t *bs); +int BotGetTeamMateCTFPreference(bot_state_t *bs, int teammate); +void BotSetTeamMateCTFPreference(bot_state_t *bs, int teammate, int preference); diff --git a/game/be_aas.h b/game/be_aas.h new file mode 100644 index 0000000..66b2efb --- /dev/null +++ b/game/be_aas.h @@ -0,0 +1,165 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_aas.h + * + * desc: Area Awareness System, stuff exported to the AI + * + * $Archive: /StarTrek/Code-DM/game/be_aas.h $ + * $Author: Jmonroe $ + * $Revision: 1 $ + * $Modtime: 1/21/00 10:12p $ + * $Date: 1/25/00 6:26p $ + * + *****************************************************************************/ + +#ifndef MAX_STRINGFIELD +#define MAX_STRINGFIELD 80 +#endif + +//travel flags +#define TFL_INVALID 0x0000001 //! 0); \ +} + +#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \ + es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1; + +static void +swapfunc(a, b, n, swaptype) + char *a, *b; + int n, swaptype; +{ + if(swaptype <= 1) + swapcode(long, a, b, n) + else + swapcode(char, a, b, n) +} + +#define swap(a, b) \ + if (swaptype == 0) { \ + long t = *(long *)(a); \ + *(long *)(a) = *(long *)(b); \ + *(long *)(b) = t; \ + } else \ + swapfunc(a, b, es, swaptype) + +#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype) + +static char * +med3(a, b, c, cmp) + char *a, *b, *c; + cmp_t *cmp; +{ + return cmp(a, b) < 0 ? + (cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a )) + :(cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c )); +} + +void +qsort(a, n, es, cmp) + void *a; + size_t n, es; + cmp_t *cmp; +{ + char *pa, *pb, *pc, *pd, *pl, *pm, *pn; + int d, r, swaptype, swap_cnt; + +loop: SWAPINIT(a, es); + swap_cnt = 0; + if (n < 7) { + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + pm = (char *)a + (n / 2) * es; + if (n > 7) { + pl = a; + pn = (char *)a + (n - 1) * es; + if (n > 40) { + d = (n / 8) * es; + pl = med3(pl, pl + d, pl + 2 * d, cmp); + pm = med3(pm - d, pm, pm + d, cmp); + pn = med3(pn - 2 * d, pn - d, pn, cmp); + } + pm = med3(pl, pm, pn, cmp); + } + swap(a, pm); + pa = pb = (char *)a + es; + + pc = pd = (char *)a + (n - 1) * es; + for (;;) { + while (pb <= pc && (r = cmp(pb, a)) <= 0) { + if (r == 0) { + swap_cnt = 1; + swap(pa, pb); + pa += es; + } + pb += es; + } + while (pb <= pc && (r = cmp(pc, a)) >= 0) { + if (r == 0) { + swap_cnt = 1; + swap(pc, pd); + pd -= es; + } + pc -= es; + } + if (pb > pc) + break; + swap(pb, pc); + swap_cnt = 1; + pb += es; + pc -= es; + } + if (swap_cnt == 0) { /* Switch to insertion sort */ + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + + pn = (char *)a + n * es; + r = min(pa - (char *)a, pb - pa); + vecswap(a, pb - r, r); + r = min(pd - pc, pn - pd - es); + vecswap(pb, pn - r, r); + if ((r = pb - pa) > es) + qsort(a, r / es, es, cmp); + if ((r = pd - pc) > es) { + /* Iterate rather than recurse to save stack space */ + a = pn - r; + n = r / es; + goto loop; + } +/* qsort(pn - r, r / es, es, cmp);*/ +} + +//================================================================================== + + +// this file is excluded from release builds because of intrinsics + +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; +} + +#if !defined ( _MSC_VER ) && ! defined ( __linux__ ) + +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; +} + + +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 + +double tan( double x ) { + return sin(x) / cos(x); +} + + +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; + + string = *stringPtr; + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + *stringPtr = string; + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + if ( string[0] != '.' ) { + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + } + + // check for decimal point + if ( c == '.' ) { + double fraction; + + fraction = 0.1; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value += c * fraction; + fraction *= 0.1; + } while ( 1 ); + + } + + // not handling 10e10 notation... + *stringPtr = string; + + return value * sign; +} + + +#if !defined( _MSC_VER ) && !defined( __linux__ ) + +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; +} + +//TiM - Required since the native QVM code can't handle UInts... >_< +//This is AddInt horribly mutilated. ;P +void AddULong( char **buf_p, unsigned long 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; +} + +//TiM - 'borrowed' from Q3 1.32 and modified so it can handle the +//rounding up system that EF has. +//I'm going to bet it was because of this id Software chose to +//round down floats from then on +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; + } + + //Com_Printf( "wholeNumber = %i, roundsUp = %i\n", fval, wholeNumber, roundsUp ); + + // write the float number + digits = 0; + val = (int)fval; + if ( fval - val < 0.0f ) + val--; + + 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 = Q_fabs(fval); //if it gets rounded up, it becomes neg, so just abs it + 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 AddFloat( char **buf_p, float fval, int width, int prec ) { + char text[32]; + int digits; + float signedVal; + char *buf; + int val; + //decimal points + float decVal; + int decScaled; + //flipping loop + char temp; + int i; + //rounding check + qboolean wholeNumber = qfalse; + qboolean roundsUp = qfalse; + float decimal = 0.9f; + + // FIXME!!!! handle fractions + //TiM - WHAT THE HELL!? YOU LEFT OUT FRACTIONS!?! WTF MAN WTF?!?! >_ 2 ) + //ioEF casts by rounding down. ( 1.5 -> 1 ) + //as this can create a sizeable variance, we have to account for it + roundsUp = (int)decimal; + + digits = 0; + signedVal = fval; + if ( fval < 0 ) { + fval = -fval; + } + + //determine if it's a whole number or not (ie = x.000 ) + wholeNumber = (fval - (float)((int)fval) == 0); + + //isolate the whole number portion of it, rounding down to maintain precision + val = (int)fval - (!wholeNumber && roundsUp ? 1 : 0); //3.452 : 4 - 1 = 3 + + //isolate the decimal number portion + decVal = (fval - (float)val); //3.452 - 3 = 0.452 + do { + decScaled = 0; + decVal *= 10.0f; //scale it up one int at a time - 0.452*10 = 4.52 + decScaled = ((int)decVal - (!wholeNumber && roundsUp ? 1 : 0)); // = 4 + //Com_Printf( "decScaled = %i\n", decScaled ); + text[digits++] = '0' + decScaled % 10; //'0' + 4 = '4' + decVal = decVal - (float)decScaled; //4.52 - 4 = 0.52 + } while ( (prec >= 0) ? digits < prec : digits < 6 ); + + //now swap it around so it's backwards (I brought it in forwards, but the whole section is done backwards) + for ( i=0; i < digits>>1; i++ ) + { + temp = text[i]; + text[i] = text[(digits-1)-i]; + text[(digits-1)-i] = temp; + } + + //add a dot + text[digits++] = '.'; + + //handle the whole number part + 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; +}*/ + + +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; + //TiM + case 'u': + AddULong( &buf_p, *(unsigned long *)arg, width, flags ); + 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/game/bg_lib.h b/game/bg_lib.h new file mode 100644 index 0000000..12187ac --- /dev/null +++ b/game/bg_lib.h @@ -0,0 +1,72 @@ +// bg_lib.h -- standard C library replacement routines used by code +// compiled for the virtual machine + +// This file is NOT included on native builds + +#ifdef Q3_VM + +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 ); + +#endif // Q3_VM diff --git a/game/bg_local.h b/game/bg_local.h new file mode 100644 index 0000000..f2b7396 --- /dev/null +++ b/game/bg_local.h @@ -0,0 +1,53 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_local.h -- local definitions for the bg (both games) files + +#define MIN_WALK_NORMAL 0.7 // can't walk on very steep slopes + +#define STEPSIZE 18 + +#define JUMP_VELOCITY 270 + +#define TIMER_LAND 130 +#define TIMER_GESTURE (34*66+50) + + +#define OVERCLIP 1.001 + +// all of the locals will be zeroed before each +// pmove, just to make damn sure we don't have +// any differences when running on client or server +typedef struct { + vec3_t forward, right, up; + float frametime; + + int msec; + + qboolean walking; + qboolean groundPlane; + trace_t groundTrace; + + float impactSpeed; + + vec3_t previous_origin; + vec3_t previous_velocity; + int previous_waterlevel; +} pml_t; + +extern pmove_t *pm; +extern pml_t pml; + +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 ); + +static void PM_StartLegsAnim( int anim ); +static void PM_StartTorsoAnim( int anim, qboolean overrideEmotes ); + +qboolean PM_SlideMove( qboolean gravity ); +void PM_StepSlideMove( qboolean gravity ); + + + diff --git a/game/bg_misc.c b/game/bg_misc.c new file mode 100644 index 0000000..01c38bc --- /dev/null +++ b/game/bg_misc.c @@ -0,0 +1,2331 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_misc.c -- both games misc functions, all completely stateless + +#include "q_shared.h" +#include "bg_public.h" + +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); +void trap_FS_FCloseFile( fileHandle_t f ); +void trap_FS_Read( void *buffer, int len, fileHandle_t f ); + +// If you change these: PLEASE CHANGE THE COMMENTS ON THE AMMO PICKUPS, WHICH DETAIL THE QUANTITY IN THE CLIP +#define AMMO_PHASER_CLIP 50 +#define AMMO_COMPRESSION_CLIP 32 +#define AMMO_IMOD_CLIP 15 +#define AMMO_SCAVENGER_CLIP 30 +#define AMMO_STASIS_CLIP 15 +#define AMMO_GRENADE_CLIP 10 +#define AMMO_TETRION_CLIP 40 +#define AMMO_QUANTUM_CLIP 6 +#define AMMO_DREADNOUGHT_CLIP 40 + +char races[256]; + +//TiM : Tidied up for programmer easability... O_o +//Marcin: not used since 30/12/2008 +/** +* Max ammo for each weapon. Unused. +*/ +int Max_Ammo[WP_NUM_WEAPONS] = +{ + 0, // WP_NONE, + + 5, // WP_NULL_HAND + + 5, // WP_TRICORDER, + 5, // WP_PADD, + 5, // WP_COFFEE, + + 5, // WP_PHASER, !! this should match PHASER_AMMO_MAX defined in bg_public + 5, // WP_COMPRESSION_RIFLE, + 5, // WP_TR116, + 5, // WP_GRENADE_LAUNCHER, + 5, // WP_QUANTUM_BURST, + 5, // WP_DISRUPTOR, + + 5, // WP_MEDKIT, + 5, // WP_VOYAGER_HYPO, + 5, // WP_DERMAL_REGEN + + 5, // WP_TOOLKIT, + 5, // WP_NEUTRINO_PROBE, + + //64 // WP_TR116 +}; + +/*int Max_Ammo[WP_NUM_WEAPONS] = +{ + 0, // WP_NONE, + 50, // WP_PHASER, !! this should match PHASER_AMMO_MAX defined in bg_public + 128, // WP_COMPRESSION_RIFLE, + 60, // WP_NULL_HAND, + 100, // WP_COFFEE, + 50, // WP_DISRUPTOR, + 30, // WP_GRENADE_LAUNCHER, + 120, // WP_TR116, + 20, // WP_QUANTUM_BURST, + 50, // WP_DERMAL_REGEN, + 50, // WP_VOYAGER_HYPO, + 50, // WP_TOOLKIT, + 100, // WP_MEDKIT, + 50, // WP_TRICORDER, + 50, // WP_PADD, + 50, // WP_NEUTRINO_PROBE, + 64 // WP_TR116 + +};*/ + +/*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, //char *classname; // spawning name + NULL, //char *pickup_sound; + NULL, //char *world_model; + NULL, //char *view_model; +/* icon */ NULL, //char *icon; +/* pickup */ NULL, //char *pickup_name; // for printing on pickup + 0, //int quantity; // for ammo how much, or duration of powerup + 0, //itemType_t giType; // IT_* flags + 0, //int giTag; +/* precache */ "", //char *precaches; // string of all models and images this item will use +/* sounds */ "" //char *sounds; // string of all sounds this item will use + }, // leave index 0 alone + + // + // WEAPONS + // + +//WP_NULL_HAND +/*QUAKED weapon_imod (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_null_hand", + "sound/silence.wav", //"sound/weapons/w_pkup.wav", + "models/weapons2/hand/hand_w.md3", //"models/weapons2/imod/imod2_w.md3",//world + "models/weapons2/hand/hand.md3", //"models/weapons2/imod/imod2.md3", //view +/* icon */ "icons/w_icon_hand", +/* pickup */ " ", + AMMO_IMOD_CLIP, + IT_WEAPON, + WP_NULL_HAND, +/* precache */ "", +/* sounds */ "" + }, + + +/*QUAKED weapon_tricorder (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_tricorder", + "sound/weapons/w_pkup.wav", + "models/weapons2/tricorder/tricorder_w.md3", //world + "models/weapons2/tricorder/tricorder.md3", //view +/* icon */ "icons/w_icon_tricorder", +/* pickup */ "Tricorder", + AMMO_PHASER_CLIP, + IT_WEAPON, + WP_TRICORDER, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_padd (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_padd", + "sound/weapons/w_pkup.wav", + "models/weapons2/padd/padd_w.md3", //world + "models/weapons2/padd/padd.md3", //view +/* icon */ "icons/w_icon_padd", +/* pickup */ "Padd", + AMMO_PHASER_CLIP, + IT_WEAPON, + WP_PADD, +/* precache */ "", +/* sounds */ "", + }, + +/*QUAKED weapon_scavenger (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_coffee", + "sound/weapons/w_pkup.wav", + "models/weapons2/coffeecup/coffee_cup_w.md3", //world + "models/weapons2/coffeecup/coffee_cup.md3", //view +/* icon */ "icons/w_icon_coffee", +/* pickup */ "Coffee, Black", + AMMO_SCAVENGER_CLIP, + IT_WEAPON, + WP_COFFEE, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_phaser (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_phaser", + "sound/weapons/w_pkup.wav", + "models/weapons2/phaser/phaser_w.md3", //world + "models/weapons2/phaser/phaser.md3", //view +/* icon */ "icons/w_icon_phaser", +/* pickup */ "Phaser", + AMMO_PHASER_CLIP, + IT_WEAPON, + WP_PHASER, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_compressionrifle (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_compressionrifle", + "sound/weapons/w_pkup.wav", + "models/weapons2/prifle/prifle_w.md3", //world + "models/weapons2/prifle/prifle.md3", //view +/* icon */ "icons/w_icon_rifle", +/* pickup */ "Phaser Compression Rifle", + AMMO_COMPRESSION_CLIP, + IT_WEAPON, + WP_COMPRESSION_RIFLE, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_tetriondisruptor (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_tr116", + "sound/weapons/w_pkup.wav", + "models/weapons2/tr116/tr-116_w.md3",//world + "models/weapons2/tr116/tr-116.md3", //view +/* icon */ "icons/w_icon_tr116", +/* pickup */ "TR-116", + AMMO_TETRION_CLIP, + IT_WEAPON, + WP_TR116, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_grenadelauncher", + "sound/weapons/w_pkup.wav", + "models/weapons2/launcher/launcher_w.md3", //world + "models/weapons2/launcher/launcher.md3", //view +/* icon */ "icons/w_icon_grenade", +/* pickup */ "Compound Grenade Launcher", + AMMO_GRENADE_CLIP, + IT_WEAPON, + WP_GRENADE_LAUNCHER, +/* precache */ "", +/* sounds */ "sound/weapons/glauncher/bounce1.wav sound/weapons/glauncher/bounce2.wav" + }, + +/*QUAKED weapon_quantumburst (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_quantumburst", + "sound/weapons/w_pkup.wav", + "models/weapons2/q_burst/q_burst_w.md3", //world + "models/weapons2/q_burst/q_burst.md3", //view +/* icon */ "icons/w_icon_quantum", +/* pickup */ "Photon Burst", + AMMO_QUANTUM_CLIP, + IT_WEAPON, + WP_QUANTUM_BURST, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_stasisweapon (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_disruptor", + "sound/weapons/w_pkup.wav", + "models/weapons2/alien_disruptor/disruptor_w.md3", //world + "models/weapons2/alien_disruptor/disruptor.md3", //view +/* icon */ "icons/w_icon_disruptor", +/* pickup */ "Disruptor", + AMMO_STASIS_CLIP, + IT_WEAPON, + WP_DISRUPTOR, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_borg_weapon (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_medkit", + "sound/weapons/w_pkup.wav", + "models/weapons2/medkit/medkit_w.md3", //world + "models/weapons2/medkit/medkit.md3", //view +/* icon */ "icons/w_icon_medkit", +/* pickup */ "Medkit", + AMMO_PHASER_CLIP, + IT_WEAPON, + WP_MEDKIT, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_voyager_hypo (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_voyager_hypo", + "sound/weapons/w_pkup.wav", + "models/weapons2/hypospray/hypospray_w.md3", //world + "models/weapons2/hypospray/hypospray.md3", //view +/* icon */ "icons/w_icon_hypo", +/* pickup */ "Hypo", + AMMO_PHASER_CLIP, + IT_WEAPON, + WP_VOYAGER_HYPO, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_dreadnought (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_dermal_regen", + "sound/weapons/w_pkup.wav", + "models/weapons2/dermal_regen/dermal_regen_w.md3", + "models/weapons2/dermal_regen/dermal_regen.md3", +/* icon */ "icons/w_icon_dermalregen", +/* pickup */ "Dermal Regenerator", + AMMO_DREADNOUGHT_CLIP, + IT_WEAPON, + WP_DERMAL_REGEN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_borg_assimilator (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_toolkit", + "sound/weapons/w_pkup.wav", + "models/weapons2/toolkit/toolkit_w.md3", //world + "models/weapons2/toolkit/toolkit.md3", //view +/* icon */ "icons/w_icon_toolkit", +/* pickup */ "Toolkit", + AMMO_PHASER_CLIP, + IT_WEAPON, + WP_TOOLKIT, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_engtool (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_hyperspanner", + "sound/weapons/w_pkup.wav", + "models/weapons2/hyperspanner/hyperspanner_w.md3", //world + "models/weapons2/hyperspanner/hyperspanner.md3", //view +/* icon */ "icons/w_icon_hyperspanner", +/* pickup */ "Hyperspanner", + AMMO_PHASER_CLIP, + IT_WEAPON, + WP_HYPERSPANNER, +/* precache */ "", +/* sounds */ "" + }, + + // + // AMMO ITEMS + // + +/*QUAKED ammo_compressionrifle (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +32 ammo for the compression rifle +*/ + { + "ammo_compressionrifle", + "sound/player/pickupenergy.wav", + "models/powerups/trek/prifle_ammo.md3", //world + NULL, +/* icon */ "icons/dm_phaser_sm", +/* pickup */ "Phaser Compression Rifle Ammo", + AMMO_COMPRESSION_CLIP, + IT_AMMO, + WP_COMPRESSION_RIFLE, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_imod (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +15 ammo for the I-MOD +*/ + { + "ammo_imod", + "sound/player/pickupenergy.wav", + "models/powerups/trek/imod_ammo.md3", //world + NULL, +/* icon */ "icons/dm_imod", +/* pickup */ "I-MOD Ammo", + AMMO_IMOD_CLIP, + IT_AMMO, + WP_NULL_HAND, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_scavenger (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +30 ammo for the scavenger rifle +*/ + { + "ammo_scavenger", + "sound/player/pickupenergy.wav", + "models/powerups/trek/scavenger_ammo.md3", //world + NULL, +/* icon */ "icons/dm_scav", +/* pickup */ "Scavenger Weapon Ammo", + AMMO_SCAVENGER_CLIP, + IT_AMMO, + WP_COFFEE, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_stasis (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +15 ammo for the stasis weapon +*/ + { + "ammo_stasis", + "sound/player/pickupenergy.wav", + "models/powerups/trek/stasis_ammo.md3", //world + NULL, +/* icon */ "icons/dm_stasis_sm", +/* pickup */ "Stasis Weapon Ammo", + AMMO_STASIS_CLIP, + IT_AMMO, + WP_DISRUPTOR, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +10 ammo for the grenade launcher +*/ + { + "ammo_grenades", + "sound/player/pickupenergy.wav", + "models/powerups/trek/glauncher_ammo.md3", //world + NULL, +/* icon */ "icons/dm_glauncher_sm", +/* pickup */ "Compound Grenade Launcher Ammo", + AMMO_GRENADE_CLIP, + IT_AMMO, + WP_GRENADE_LAUNCHER, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_tetriondisruptor (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +40 ammo for the tetrYon disruptor +*/ + { + "ammo_tetriondisruptor", + "sound/player/pickupenergy.wav", + "models/powerups/trek/tetrion_ammo.md3", //world + NULL, +/* icon */ "icons/dm_tetrion_sm", +/* pickup */ "Tetryon Pulse Disruptor Ammo", + AMMO_TETRION_CLIP, + IT_AMMO, + WP_TR116, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_quantumburst (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +6 ammo for the quantum burst weapon +*/ + { + "ammo_quantumburst", + "sound/player/pickupenergy.wav", + "models/powerups/trek/torpedo.md3", //world + NULL, +/* icon */ "icons/dm_torpedo_sm", +/* pickup */ "Photon Burst Ammo", + AMMO_QUANTUM_CLIP, + IT_AMMO, + WP_QUANTUM_BURST, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_dreadnought (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +40 ammo for the dreadnought/arc welder +*/ + { + "ammo_dreadnought", + "sound/player/pickupenergy.wav", + "models/powerups/trek/arc_ammo.md3", //world + NULL, +/* icon */ "icons/dm_a_arc_sm", +/* pickup */ "Dermal Regenerator Ammo", + AMMO_DREADNOUGHT_CLIP, + IT_AMMO, + WP_DERMAL_REGEN, +/* precache */ "", +/* sounds */ "" + }, + + // + // ARMOR + // + +/*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +5 points of shields +*/ + { + "item_armor_shard", + "sound/player/pickupenergy.wav", + "models/powerups/trek/armor_shard.md3", //world + NULL, +/* icon */ "icons/icon_shards", +/* pickup */ "Incremental Shield Boost", + 5, + IT_ARMOR, + 0, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +50 points of shields +*/ + { + "item_armor_combat", + "sound/player/pickupenergy.wav", + "models/powerups/trek/armor.md3", //world + NULL, +/* icon */ "icons/dm_armor_sm", +/* pickup */ "Personal Deflector Screen", + 50, + IT_ARMOR, + 0, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +100 points of shields +*/ + { + "item_armor_body", + "sound/player/suitenergy.wav", + "models/powerups/trek/armor2.md3", //world + NULL, +/* icon */ "icons/dm_superarmor_sm", +/* pickup */ "Isokinetic Deflector Screen", + 100, + IT_ARMOR, + 0, +/* precache */ "", +/* sounds */ "" + }, + + // + // HEALTH + // + +/*QUAKED item_hypo_small (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +5 points of health, max of 200 +*/ + { + "item_hypo_small", + "sound/player/pickuphealth.wav", + "models/powerups/trek/hypo_single.md3", //world + NULL, +/* icon */ "icons/dm_health_sm", +/* pickup */ "Booster Hypospray", + 5, + IT_HEALTH, + 0, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_hypo (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +25 points of health, max of 100 +*/ + { + "item_hypo", + "sound/player/suithealth.wav", + "models/powerups/trek/hypo_double.md3", //world + NULL, +/* icon */ "icons/dm_health2_sm", +/* pickup */ "Emergency Hypospray", + 25, + IT_HEALTH, + 0, +/* precache */ "", +/* sounds */ "" + }, + + // + // HOLDABLE ITEMS + // + +/*QUAKED holdable_transporter (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +pick it up and it stays in your inventory until used, at which time you drop it in front of you and it still +kind of resides in your inventory. when you use it _again_ it activates and anyone can walk through the transporter. +*/ + { + "holdable_transporter", + "sound/items/holdable.wav", + "models/powerups/trek/transporter.md3", //world + NULL, +/* icon */ "icons/dm_transport_sm", +/* pickup */ "Personal Transporter Device", + 60, + IT_HOLDABLE, + HI_TRANSPORTER, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED holdable_medkit (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +pick it up and it stays in your inventory until used, at which time it sets your health to 100 +*/ + { + "holdable_medkit", + "sound/items/holdable.wav", + "models/powerups/trek/med_kit.md3", //world + NULL, +/* icon */ "icons/dm_health3_sm", +/* pickup */ "Portable Medkit", + 60, + IT_HOLDABLE, + HI_MEDKIT, +/* precache */ "", +/* sounds */ "sound/items/use_medkit.wav" + }, + + // + // POWERUP ITEMS + // + + +//Transporter ent - quad no more +/*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +multiplies your weapon's damage for 30 seconds +*/ +// { +// "item_quad", +// "sound/items/quaddamage.wav", +// "models/powerups/trek/quad_damage.md3", //world +// NULL, +///* icon */ "icons/dm_quad", +///* pickup */ "Quantum Weapon Enhancer", +// 30, +// IT_POWERUP, +// PW_QUAD, +///* precache */ "", +///* sounds */ "sound/items/damage3.wav" +// }, + +/*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +20 seconds of invulnerability +*/ +// { +// "item_enviro", +// "sound/items/protect.wav", +// "models/powerups/trek/armor3.md3", //world +// NULL, +///* icon */ "icons/envirosuit", +///* pickup */ "Metaphasic Shielding", +// 20, +// IT_POWERUP, +// PW_BOLTON, +///* precache */ "", +///* sounds */ "sound/items/protect3.wav" +// }, + +/*QUAKED item_haste (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +for 30 seconds you run at %150 of your normal speed and your firing delays are 3/4 as long +*/ + { + "item_haste", + "sound/items/haste.wav", + "models/powerups/trek/haste.md3", //world + NULL, +/* icon */ "icons/dm_haste", +/* pickup */ "Temporal Accelerator", + 30, + IT_POWERUP, + PW_HASTE, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_invis (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +20 seconds of invisibility +*/ + { + "item_invis", + "sound/items/invisibility.wav", + "models/powerups/trek/invisible.md3", //world + NULL, +/* icon */ "icons/dm_invisibility", +/* pickup */ "Personal Cloaking Device", + 20, + IT_POWERUP, + PW_INVIS, +/* precache */ "", +/* sounds */ "" + }, + +//TiM : No regen. it's a laser now +/*QUAKED item_regen (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +for 30 seconds you get 5 health every second, up to 200 health +*/ +// { +// "item_regen", +// "sound/items/regeneration.wav", +// "models/powerups/trek/regen.md3", //world +// NULL, +///* icon */ "icons/regen", +///* pickup */ "Nano-Regenerative Protoplasmer", +// 30, +// IT_POWERUP, +// PW_LASER, +///* precache */ "", +///* sounds */ "sound/items/regen.wav" +// }, + +/*QUAKED item_flight (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +30 seconds of flight +*/ + { + "item_flight", + "sound/items/flight.wav", + "models/powerups/trek/flight.md3", //world + NULL, +/* icon */ "icons/dm_flight", +/* pickup */ "Anti-Gravity Pack", + 30, + IT_POWERUP, + PW_FLIGHT, +/* precache */ "", +/* sounds */ "sound/items/flight.wav" + }, + +/*QUAKED team_CTF_redflag (1 0 0) (-24 -24 -16) (24 24 32) +Only in CTF games +*/ + //{ + // "team_CTF_redflag", + // "sound/voice/computer/misc/haveflag.wav", + // "models/flags/flag_red.md3", //world !! must match cg_main media and botfiles/items.c !! + // NULL, +///* icon */ "icons/iconf_red", +///* pickup */ "Red Flag", +// 0, +// IT_TEAM, +// PW_REDFLAG, +///* precache */ "", +///* sounds */ "sound/voice/computer/misc/stolen.wav sound/voice/computer/misc/stolen_e.wav sound/voice/computer/misc/returned.wav sound/voice/computer/misc/returned_e.wav" +// }, + +/*QUAKED team_CTF_blueflag (0 0 1) (-24 -24 -16) (24 24 32) +Only in CTF games +*/ +// { +// "team_CTF_blueflag", +// "sound/voice/computer/misc/haveflag.wav", + // "models/flags/flag_blue.md3",//must match cg_main media and botfiles/items.c +// NULL, +///* icon */ "icons/iconf_blu", +///* pickup */ "Blue Flag", +// 0, +// IT_TEAM, +// PW_REDFLAG, +///* precache */ "", +///* sounds */ "sound/voice/computer/misc/dropped.wav sound/voice/computer/misc/dropped_e.wav sound/voice/computer/misc/scored.wav sound/voice/computer/misc/scored_e.wav" +// }, + +/*QUAKED holdable_detpack (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +BLAMMO! +*/ + { + "holdable_detpack", + "sound/player/pickupenergy.wav", + "models/powerups/trek/detpak.md3", //world + NULL, +/* icon */ "icons/icon_detpack", +/* pickup */ "Ultritium Explosive Charge", + 1, // 5, + IT_HOLDABLE, + HI_DETPACK, +/* precache */ "", +/* sounds */ "sound/weapons/detpacklatch.wav sound/weapons/explosions/detpakexplode.wav" + }, + +//TiM No more seeker - flashlight now +/*QUAKED item_seeker (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +30 seconds of seeker drone +*/ +// { +// "item_seeker", +// "sound/player/pickupenergy.wav", +// "models/powerups/trek/flyer.md3", //world +// NULL, +///* icon */ "icons/icon_seeker", +///* pickup */ "Seeker Drone", +// 30, +// IT_POWERUP, +// PW_FLASHLIGHT, +///* precache */ "", +///* sounds */ "" +// }, + +/*QUAKED holdable_shield (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +About 25 seconds or 250 hit points of a portashield. +*/ + { + "holdable_shield", + "sound/player/pickupenergy.wav", + "models/powerups/trek/shield_gen.md3", //world + NULL, +/* icon */ "icons/icon_shield", +/* pickup */ "Portable Force Field", + 1, + IT_HOLDABLE, + HI_SHIELD, +/* precache */ "", +/* sounds */ "sound/weapons/detpacklatch.wav sound/movers/forceup.wav sound/ambience/spark5.wav" + }, + + +/*QUAKED Holographic_decoy (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +About 1 minute of a holographic decoy. +*/ + { + "Holographic_decoy", + "sound/items/holdable.wav", + "models/powerups/trek/decoy.md3", //world + NULL, +/* icon */ "icons/icon_decoy", +/* pickup */ "Holographic Decoy", + 1, + IT_HOLDABLE, + HI_DECOY, +/* precache */ "", +/* sounds */ "" + }, + + // + // New Weapons + // + +/*QUAKED weapon_tr116 (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ +// { +// "weapon_tr116", +// "sound/weapons/w_pkup.wav", +// "models/weapons2/tr116/tr116_w.md3", //world +// "models/weapons2/tr116/tr116.md3", //view +///* icon */ "icons/w_icon_tr116", +///* pickup */ "TR-116", +// AMMO_PHASER_CLIP, +// IT_WEAPON, +// WP_TR116, +///* precache */ "", +///* sounds */ "" +// }, + + // end of list marker + {NULL} +}; + +int bg_numItems = sizeof(bg_itemlist) / sizeof(bg_itemlist[0]) - 1; + +#define STAND_VIEWHEIGHT (DEFAULT_VIEWHEIGHT) +#define SITTING_VIEWHEIGHT (22) //TiM: To reduce redundancy here +#define CROUCHING_VIEWHEIGHT (CROUCH_VIEWHEIGHT) +#define HITBOX_DEFAULT 32 +#define HITBOX_CROUCH 16 +#define HITBOX_NULL -23 //0 //-24 doesn't work on patch meshes apparently. That might be more the mapper's fault than mine tho lol +#define NULL_ANIM -1 + +//!Main emotes definition arrayzor +//Suffice it to say... my hands hurt after writing this thing >.< +emoteList_t bg_emoteList[] = { + //name //type //enumName //enumLoop //viewHeight //hitBox Height //bodyFlags //animFlags + { "alert", TYPE_MISC, -1, NULL_ANIM, 0, 0, 0, 0, }, + { "alert2", TYPE_MISC, -1, NULL_ANIM, 0, 0, 0, 0, }, + { "assimilated", TYPE_FULLBODY, BOTH_ASSIMILATED1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_ALL | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "", TYPE_NONE, -1, NULL_ANIM, 0, 0, 0, 0, }, + { "benchsit1_2stand", TYPE_SITTING, BOTH_BENCHSIT1_2STAND, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "benchsit1_fixboot", TYPE_SITTING, BOTH_BENCHSIT1_FIXBOOT, BOTH_BENCHSIT1_IDLE, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_REVERTLOOP_BOTH }, + { "benchsit1_idle", TYPE_SITTING, BOTH_BENCHSIT1_IDLE, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_LOOP_BOTH | EMOTE_OVERRIDE_BOTH }, + { "benchsit1to2", TYPE_SITTING, BOTH_BENCHSIT1TO2, BOTH_BENCHSIT2_IDLE, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "benchsit2_idle", TYPE_SITTING, BOTH_BENCHSIT2_IDLE, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_LOOP_BOTH | EMOTE_OVERRIDE_BOTH }, + { "benchsit2to1", TYPE_SITTING, BOTH_BENCHSIT2TO1, BOTH_BENCHSIT1_IDLE, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "catch1", TYPE_FULLBODY, BOTH_CATCH1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "console1", TYPE_CONSOLE, BOTH_CONSOLE1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH }, + { "console1_idle", TYPE_CONSOLE, BOTH_CONSOLE1IDLE, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "console1_left", TYPE_CONSOLE, BOTH_CONSOLE1LEFT, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH }, + { "console1_right", TYPE_CONSOLE, BOTH_CONSOLE1RIGHT, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH }, + { "console2", TYPE_CONSOLE, BOTH_CONSOLE2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_LOOP_BOTH | EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "console3", TYPE_CONSOLE, BOTH_CONSOLE3, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH }, + { "console3_idle", TYPE_CONSOLE, BOTH_CONSOLE3IDLE, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH }, + { "console3_left", TYPE_CONSOLE, BOTH_CONSOLE3LEFT, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH }, + { "console3_right", TYPE_CONSOLE, BOTH_CONSOLE3RIGHT, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH }, + { "console4", TYPE_CONSOLE, BOTH_CONSOLE4, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH }, + { "console5", TYPE_CONSOLE, BOTH_CONSOLE5, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH }, + { "couchsit1_2stand", TYPE_SITTING, BOTH_COUCHSIT1_2STAND1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "couchsit1_left", TYPE_SITTING, BOTH_COUCHSIT1_GESTURELEFT, BOTH_COUCHSIT1_IDLE, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "couchsit1_right", TYPE_SITTING, BOTH_COUCHSIT1_GESTURERIGHT,BOTH_COUCHSIT1_IDLE, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "couchsit1_idle", TYPE_SITTING, BOTH_COUCHSIT1_IDLE, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "couchsit1_talk", TYPE_SITTING, BOTH_COUCHSIT1_TALKGESTURE, BOTH_COUCHSIT1_IDLE, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "couchsit2to1", TYPE_SITTING, BOTH_COUCHSIT1_TO2, BOTH_COUCHSIT1_IDLE, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_REVERTLOOP_UPPER }, + { "couchsit2_idle", TYPE_SITTING, BOTH_COUCHSIT2, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "coverup1_end", TYPE_FULLBODY, BOTH_COVERUP1_END, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER }, + { "coverup1_loop", TYPE_FULLBODY, BOTH_COVERUP1_LOOP, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH }, + { "coverup1_start", TYPE_FULLBODY, BOTH_COVERUP1_START, BOTH_COVERUP1_LOOP, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER }, + { "cowar1", TYPE_FULLBODY, BOTH_COWAR1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "crowdlook1", TYPE_FULLBODY, BOTH_CROWDLOOK1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "crowdlook2", TYPE_FULLBODY, BOTH_CROWDLOOK2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "crowdlook3", TYPE_MISC, BOTH_CROWDLOOK3, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH | EMOTE_LOOP_BOTH }, + { "crowdlook4", TYPE_MISC, BOTH_CROWDLOOK4, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "dive1", TYPE_FULLBODY, BOTH_DIVE1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_ALL | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "eyes_shut", TYPE_MISC, -1, 0, 0, 0, 0, 0, }, + { "eyes_angry", TYPE_MISC, -1, 0, 0, 0, 0, 0, }, + { "gesture2", TYPE_GESTURE, BOTH_GESTURE2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "gesture3", TYPE_GESTURE, BOTH_GESTURE3, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "get_up1", TYPE_FULLBODY, BOTH_GET_UP1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "grab1", TYPE_FULLBODY, BOTH_GRAB1, BOTH_GRAB2, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "grab2", TYPE_FULLBODY, BOTH_GRAB2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "grab3", TYPE_FULLBODY, BOTH_GRAB3, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER }, + { "grab4", TYPE_FULLBODY, BOTH_GRAB4, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH }, + { "grabbed1", TYPE_FULLBODY, BOTH_GRABBED1, BOTH_GRABBED2, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "grabbed2", TYPE_FULLBODY, BOTH_GRABBED2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "groundshake1", TYPE_FULLBODY, BOTH_GROUNDSHAKE1, BOTH_GROUNDSHAKE1LOOP, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "groundshake1loop", TYPE_FULLBODY, BOTH_GROUNDSHAKE1LOOP, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "groundshake2", TYPE_FULLBODY, BOTH_GROUNDSHAKE2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "guard_idle1", TYPE_MISC, BOTH_GUARD_IDLE1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "guard_lkrt1", TYPE_MISC, BOTH_GUARD_LKRT1, BOTH_GUARD_IDLE1, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "guard_lookaround1", TYPE_MISC, BOTH_GUARD_LOOKAROUND1, BOTH_GUARD_IDLE1, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "guilt1", TYPE_FULLBODY, BOTH_GUILT1, NULL_ANIM, CROUCHING_VIEWHEIGHT, HITBOX_CROUCH, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "hitwall1", TYPE_FULLBODY, BOTH_HITWALL1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "help1", TYPE_FULLBODY, BOTH_HELP1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "injured1", TYPE_INJURED, BOTH_INJURED1, NULL_ANIM, DEAD_VIEWHEIGHT, HITBOX_CROUCH, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH, }, + { "injured2", TYPE_INJURED, BOTH_INJURED2, NULL_ANIM, DEAD_VIEWHEIGHT, HITBOX_CROUCH, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH, }, + { "injured3", TYPE_INJURED, BOTH_INJURED3, NULL_ANIM, DEAD_VIEWHEIGHT, HITBOX_CROUCH, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH, }, + { "injured4", TYPE_INJURED, BOTH_INJURED4, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH, }, + { "injured4to5", TYPE_INJURED, BOTH_INJURED4TO5, BOTH_INJURED5, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "injured5", TYPE_INJURED, BOTH_INJURED5, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "injured6", TYPE_INJURED, BOTH_INJURED6, NULL_ANIM, DEAD_VIEWHEIGHT, HITBOX_CROUCH, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "injured6_combadge", TYPE_INJURED, BOTH_INJURED6COMBADGE, BOTH_INJURED6, DEAD_VIEWHEIGHT, HITBOX_CROUCH, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "injured6_point", TYPE_INJURED, BOTH_INJURED6POINT, BOTH_INJURED6, DEAD_VIEWHEIGHT, HITBOX_CROUCH, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "kneel_hand1", TYPE_MISC, BOTH_KNEELHAND1, NULL_ANIM, CROUCHING_VIEWHEIGHT, HITBOX_CROUCH, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "laugh1", TYPE_FULLBODY, BOTH_LAUGH2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER }, + { "laugh2", TYPE_FULLBODY, BOTH_LAUGH1, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "lean1", TYPE_MISC, BOTH_LEAN1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "pain2writhe1", TYPE_FULLBODY, BOTH_PAIN2WRITHE1, BOTH_WRITHING1, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "possessed1", TYPE_FULLBODY, BOTH_POSSESSED1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "possessed2", TYPE_FULLBODY, BOTH_POSSESSED2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH }, + { "psychicshock1", TYPE_FULLBODY, BOTH_PSYCHICSHOCK1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "psychicshock2", TYPE_FULLBODY, BOTH_PSYCHICSHOCK2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "scared2", TYPE_FULLBODY, BOTH_SCARED2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER }, + { "shield1", TYPE_FULLBODY, BOTH_SHIELD1, BOTH_SHIELD2, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER }, + { "shield2", TYPE_FULLBODY, BOTH_SHIELD2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_BOTH }, + { "sit1stand", TYPE_SITTING, BOTH_SIT1STAND, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_BOTH }, + { "sit1to2", TYPE_SITTING, BOTH_SIT1TO2, BOTH_SIT2, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "sit1to3", TYPE_SITTING, BOTH_SIT1TO3, BOTH_SIT3, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "sit1", TYPE_SITTING, BOTH_SIT1, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "sit2to1", TYPE_SITTING, BOTH_SIT2TO1, BOTH_SIT1, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "sit2to3", TYPE_SITTING, BOTH_SIT2TO3, BOTH_SIT3, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "sit2", TYPE_SITTING, BOTH_SIT2, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "sit3to1", TYPE_SITTING, BOTH_SIT3TO1, BOTH_SIT1, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "sit3to2", TYPE_SITTING, BOTH_SIT3TO2, BOTH_SIT2, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "sit3", TYPE_SITTING, BOTH_SIT3, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "sit4to5", TYPE_SITTING, BOTH_SIT4TO5, BOTH_SIT5, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "sit4to6", TYPE_SITTING, BOTH_SIT4TO6, BOTH_SIT6, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "sit4", TYPE_SITTING, BOTH_SIT4, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "sit5to4", TYPE_SITTING, BOTH_SIT5TO4, BOTH_SIT4, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "sit5to6", TYPE_SITTING, BOTH_SIT5TO6, BOTH_SIT6, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "sit5", TYPE_SITTING, BOTH_SIT5, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "sit6to4", TYPE_SITTING, BOTH_SIT6TO4, BOTH_SIT4, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "sit6to5", TYPE_SITTING, BOTH_SIT6TO5, BOTH_SIT5, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "sit6", TYPE_SITTING, BOTH_SIT6, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "sit7", TYPE_SITTING, BOTH_SIT7, NULL_ANIM, DEAD_VIEWHEIGHT, HITBOX_CROUCH, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_LOOP_BOTH, }, + { "sit7tostand1", TYPE_SITTING, BOTH_SIT7TOSTAND1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "sleep1", TYPE_MISC, BOTH_SLEEP1, NULL_ANIM, DEAD_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "sleep1_nose", TYPE_MISC, BOTH_SLEEP1_NOSE, BOTH_SLEEP1, DEAD_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "sleep1_getup", TYPE_MISC, BOTH_SLEEP1GETUP, NULL_ANIM, DEAD_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "sleep2", TYPE_MISC, BOTH_SLEEP2, NULL_ANIM, DEAD_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "sleep2_shift", TYPE_MISC, BOTH_SLEEP2_SHIFT, BOTH_SLEEP2, DEAD_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "sleep2_getup", TYPE_MISC, BOTH_SLEEP2GETUP, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "sleep3", TYPE_MISC, BOTH_SLEEP3, NULL_ANIM, DEAD_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "sleep3getup", TYPE_MISC, BOTH_SLEEP3GETUP, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "snapto1", TYPE_FULLBODY, BOTH_SNAPTO1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "snapto2", TYPE_FULLBODY, BOTH_SNAPTO2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand1_random1", TYPE_GESTURE, BOTH_STAND1_RANDOM2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand1_random2", TYPE_GESTURE, BOTH_STAND1_RANDOM3, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand1_random3", TYPE_GESTURE, BOTH_STAND1_RANDOM4, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand1_random4", TYPE_GESTURE, BOTH_STAND1_RANDOM5, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand1_random5", TYPE_GESTURE, BOTH_STAND1_RANDOM6, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand1_random6", TYPE_GESTURE, BOTH_STAND1_RANDOM7, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand1_random7", TYPE_GESTURE, BOTH_STAND1_RANDOM8, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand1_random8", TYPE_GESTURE, BOTH_STAND1_RANDOM9, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand1_random9", TYPE_GESTURE, BOTH_STAND1_RANDOM10, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand1_random10", TYPE_GESTURE, BOTH_STAND1_RANDOM11, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand2_random1", TYPE_GESTURE, BOTH_STAND2_RANDOM1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand2_random2", TYPE_GESTURE, BOTH_STAND2_RANDOM2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand2_random3", TYPE_GESTURE, BOTH_STAND2_RANDOM3, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand2_random4", TYPE_GESTURE, BOTH_STAND2_RANDOM4, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand2_random5", TYPE_GESTURE, BOTH_STAND2_RANDOM5, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand2_random6", TYPE_GESTURE, BOTH_STAND2_RANDOM6, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand2_random7", TYPE_GESTURE, BOTH_STAND2_RANDOM7, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand2_random8", TYPE_GESTURE, BOTH_STAND2_RANDOM8, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand2_random9", TYPE_GESTURE, BOTH_STAND2_RANDOM9, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand2_random10", TYPE_GESTURE, BOTH_STAND2_RANDOM10, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand2_random11", TYPE_GESTURE, BOTH_STAND2_RANDOM11, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand2_random12", TYPE_GESTURE, BOTH_STAND2_RANDOM12, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + { "stand3", TYPE_MISC, BOTH_STAND3, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "standup1", TYPE_FULLBODY, BOTH_STANDUP1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "surprised1", TYPE_FULLBODY, BOTH_SURPRISED1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER }, + { "surprised2", TYPE_FULLBODY, BOTH_SURPRISED2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER }, + { "surprised3", TYPE_FULLBODY, BOTH_SURPRISED3, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER }, + { "surprised4", TYPE_FULLBODY, BOTH_SURPRISED4, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH }, + { "surprised5", TYPE_FULLBODY, BOTH_SURPRISED5, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER }, + { "table_eat1", TYPE_MISC, BOTH_TABLE_EAT1, BOTH_TABLE_IDLE1, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER }, + { "table_getup1", TYPE_MISC, BOTH_TABLE_GETUP1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER }, + { "table_idle1", TYPE_MISC, BOTH_TABLE_IDLE1, NULL_ANIM, SITTING_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "table_talkgesture1", TYPE_MISC, BOTH_TABLE_TALKGESTURE1, BOTH_TABLE_IDLE1, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_UPPER }, + { "talkgesture1", TYPE_GESTURE, BOTH_TALKGESTURE1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_DEFAULT, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "talkgesture2", TYPE_GESTURE, BOTH_TALKGESTURE2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "talkgesture3", TYPE_GESTURE, TORSO_TALKGESTURE4, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "talkgesture4", TYPE_GESTURE, TORSO_TALKGESTURE5, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "writhing2", TYPE_FULLBODY, BOTH_WRITHING2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_BOTH, EMOTE_CLAMP_BODY | EMOTE_OVERRIDE_BOTH | EMOTE_LOOP_BOTH }, + { "combadge1", TYPE_GESTURE, TORSO_COMBADGE1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "combadge2", TYPE_GESTURE, TORSO_COMBADGE2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "combadge3", TYPE_GESTURE, TORSO_COMBADGE3, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "combadge4", TYPE_GESTURE, TORSO_COMBADGE4, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "equipment1", TYPE_GESTURE, TORSO_EQUIPMENT1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "equipment2", TYPE_GESTURE, TORSO_EQUIPMENT2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "equipment3", TYPE_GESTURE, TORSO_EQUIPMENT3, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "grablbackl", TYPE_GESTURE, TORSO_GRABLBACKL, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "hand1", TYPE_GESTURE, TORSO_HAND1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "hand2", TYPE_GESTURE, TORSO_HAND2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "handgesture1", TYPE_GESTURE, TORSO_HANDGESTURE1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "handgesture2", TYPE_GESTURE, TORSO_HANDGESTURE2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "handgesture3", TYPE_GESTURE, TORSO_HANDGESTURE3, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "handgesture4", TYPE_GESTURE, TORSO_HANDGESTURE4, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "handgesture5", TYPE_GESTURE, TORSO_HANDGESTURE5, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "handgesture6", TYPE_GESTURE, TORSO_HANDGESTURE6, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "handgesture7", TYPE_GESTURE, TORSO_HANDGESTURE7, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "handgesture8", TYPE_GESTURE, TORSO_HANDGESTURE8, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "handgesture9", TYPE_GESTURE, TORSO_HANDGESTURE9, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "handgesture10", TYPE_GESTURE, TORSO_HANDGESTURE10, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "handgesture11", TYPE_GESTURE, TORSO_HANDGESTURE11, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "handgesture12", TYPE_GESTURE, TORSO_HANDGESTURE12, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "handgesture13", TYPE_GESTURE, TORSO_HANDGESTURE13, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "hypospray", TYPE_GESTURE, TORSO_HYPOSPRAY1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "pokeridle1", TYPE_GESTURE, TORSO_POKERIDLE1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_UPPER }, + { "pokeridle2", TYPE_GESTURE, TORSO_POKERIDLE2, TORSO_POKERIDLE1, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER }, + { "pokeridle3", TYPE_GESTURE, TORSO_POKERIDLE3, TORSO_POKERIDLE1, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "shout1", TYPE_GESTURE, TORSO_SHOUT1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_UPPER }, + { "speechless1", TYPE_GESTURE, TORSO_SPEECHLESS1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_UPPER }, + { "speechless2", TYPE_GESTURE, TORSO_SPEECHLESS2, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_LOOP_UPPER }, + { "taunt", TYPE_GESTURE, TORSO_GESTURE, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + { "wrist1", TYPE_GESTURE, TORSO_WRIST1, NULL_ANIM, STAND_VIEWHEIGHT, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, +}; + + //{ "gesture1", BOTH_GESTURE1, NULL_ANIM, 0, 0, MOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + //{ "stand1_random1", BOTH_STAND1_RANDOM1, NULL_ANIM, 0, 0, EMOTE_BOTH, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_BOTH }, + //{ "talkgesture5", TORSO_TALKGESTURE6, NULL_ANIM, 0, HITBOX_NULL, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + //{ "headshake1", TORSO_HEADSHAKE1, NULL_ANIM, 0, 0, EMOTE_UPPER, EMOTE_OVERRIDE_UPPER | EMOTE_REVERTLOOP_UPPER }, + + +//TiM : Hrmm... this may have been why it was crashing on some people's PCs... +//Let's try the old fashioned way... +//int bg_numEmotes = sizeof( emoteList ) / sizeof ( emoteList[0] ); +int bg_numEmotes = 180; + +//TiM: +//!In order to set up a list of items we can use in the 'give' command. +giveItem_t bg_giveItem[] = { + //consoleName giveType giveValue + { "all", TYPE_ALL, 0 }, + { "health", TYPE_HEALTH, 0 }, + { "weapons", TYPE_WEAPONS, 0 }, + { "ammo", TYPE_AMMO, 0 }, + { "transporter", TYPE_HOLDABLE, HI_TRANSPORTER }, + { "forcefield", TYPE_HOLDABLE, HI_SHIELD }, + { "phaser", TYPE_WEAPON, WP_PHASER }, + { "phaser_rifle", TYPE_WEAPON, WP_COMPRESSION_RIFLE }, + { "coffee", TYPE_WEAPON, WP_COFFEE }, + { "disruptor", TYPE_WEAPON, WP_DISRUPTOR }, + { "coffee", TYPE_WEAPON, WP_COFFEE }, + { "admin_gun", TYPE_WEAPON, WP_GRENADE_LAUNCHER }, + { "tr-116", TYPE_WEAPON, WP_TR116 }, + { "photon_burst", TYPE_WEAPON, WP_QUANTUM_BURST }, + { "dermal_regen", TYPE_WEAPON, WP_DERMAL_REGEN }, + { "hypospray", TYPE_WEAPON, WP_VOYAGER_HYPO }, + { "toolkit", TYPE_WEAPON, WP_TOOLKIT }, + { "medkit", TYPE_WEAPON, WP_MEDKIT }, + { "tricorder", TYPE_WEAPON, WP_TRICORDER }, + { "padd", TYPE_WEAPON, WP_PADD }, + { "hyperspanner", TYPE_WEAPON, WP_HYPERSPANNER }, + { "cloak", TYPE_POWERUP, 0 }, + { "flight", TYPE_POWERUP, 0 }, + { "god", TYPE_POWERUP, 0 } +}; + +//TiM - Meh... just define the number... doing fancy array stuff seems to make PCs 'splode O_o +int bg_numGiveItems = 24; + +/** +* \brief Finds an item by it's classname +* +* \return the item +*/ +gitem_t *BG_FindItemWithClassname(const char *name) +{ + int i = 0; + gitem_t *item = NULL; + + if ( (NULL == name) || (0 == name[0]) ) + { + return NULL; + } + for (i = 0; i < bg_numItems; i++) + { + item = &bg_itemlist[i]; + if (!strcmp(name, item->classname)) + { + return item; + } + } + return NULL; +} + +/** +* Finds the classname for a holdable. +* \return classname for holdable +*/ +char *BG_FindClassnameForHoldable(holdable_t pw) +{ + gitem_t *item = BG_FindItemForHoldable(pw); + + if (item) + { + return item->classname; + } + return NULL; +} + +/** +* Finds item for a powerup. +* \return the item +*/ +gitem_t *BG_FindItemForPowerup( powerup_t pw ) { + int i; + + for ( i = 0 ; i < bg_numItems ; i++ ) { + if ( (bg_itemlist[i].giType == IT_POWERUP || + bg_itemlist[i].giType == IT_TEAM) && + bg_itemlist[i].giTag == pw ) { + return &bg_itemlist[i]; + } + } + + return NULL; +} + + +/** +* Finds item for a holdable. +* \return the item +*/ +gitem_t *BG_FindItemForHoldable( holdable_t pw ) { + int i; + + for ( i = 0 ; i < bg_numItems ; i++ ) { + if ( bg_itemlist[i].giType == IT_HOLDABLE && bg_itemlist[i].giTag == pw ) { + return &bg_itemlist[i]; + } + } + + Com_Error( ERR_DROP, "HoldableItem not found" ); + + return NULL; +} + + +/** +* Finds item for a weapon. +* \return the item +*/ +gitem_t *BG_FindItemForWeapon( weapon_t weapon ) { + gitem_t *it; + //if(ent->client->sess.sessionClass){ + //} + 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; +} + +/** +* Finds item for ammo. +* \return the item +*/ +gitem_t *BG_FindItemForAmmo( weapon_t weapon ) { + gitem_t *it; + + for ( it = bg_itemlist + 1 ; it->classname ; it++) { + if ( it->giType == IT_AMMO && it->giTag == weapon ) { + return it; + } + } + + Com_Error( ERR_DROP, "Couldn't find item for ammo %i", weapon); + return NULL; +} + +/** +* Find a tiem by pickupName. +* \return the item +*/ +gitem_t *BG_FindItem( const char *pickupName/*const char *classname*/ ) { + gitem_t *it; + + for ( it = bg_itemlist + 1 ; it->classname ; it++ ) { + if ( !Q_stricmp( it->pickup_name, pickupName )/*!Q_stricmp( it->classname, classname)*/ )//RPG-X: RedTechie - Trying to fix give cmd + return it; + } + + return NULL; +} + +/** +* \brief Checks if player is touching an item. +* +* 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] > 44 + || ps->origin[0] - origin[0] < -50 + || ps->origin[1] - origin[1] > 36 + || ps->origin[1] - origin[1] < -36 + || ps->origin[2] - origin[2] > 36 + || ps->origin[2] - origin[2] < -36 ) { + return qfalse; + } + + return qtrue; +} + + + +/** +* \brief Check if item can be grabbed. +* +* Returns false if the item should not be picked up. +* This needs to be the same for client side prediction and server use. +*/ + +qboolean BG_CanItemBeGrabbed( const entityState_t *ent, const playerState_t *ps, int maxWeap ) { + gitem_t *item; + +//_______________________________________________________________ +/* SYNC with g_items global var +int Max_Ammo[WP_NUM_WEAPONS] = +{ + 0, // WP_NONE, + 50, // WP_PHASER, !! this should match PHASER_AMMO_MAX defined in bg_public + 128, // WP_COMPRESSION_RIFLE, + 60, // WP_NULL_HAND, + 100, // WP_COFFEE, + 50, // WP_DISRUPTOR, + 30, // WP_GRENADE_LAUNCHER, + 120, // WP_TR116, + 20, // WP_QUANTUM_BURST, + 120, // WP_DERMAL_REGEN, + 100, // WP_VOYAGER_HYPO, + 100, // WP_TOOLKIT, + 100 // WP_MEDKIT + +}; +//_______________________________________________________________ +*/ + + if ( ent->modelindex < 1 || ent->modelindex >= bg_numItems ) { + //Com_Error( ERR_DROP, "BG_CanItemBeGrabbed: index out of range" ); + //Com_Printf ("BG_CanItemBeGrabbed: index out of range\n"); + return qfalse; + } + + // The player used to not be able to pickup stuff when ghosted. Now it automatically unghosts them. + //RPG-X: RedTechie - No we want to keep ghost >:) + /*if (ps->powerups[PW_GHOST]) + { + return qfalse; + }*/ + + item = &bg_itemlist[ent->modelindex]; + + //if ( ps->persistant[PERS_CLASS] == PC_BORG && item->giType != IT_TEAM )//FIXME: check for pModAssimilation mode being on + //{//borg cannot pick up anything but flags + // return qfalse; + //} + + // Marcin| 30/12/2008 + if (ps->ammo[item->giTag] >= maxWeap) { + return qfalse; + } else { + return qtrue; + } + + //unused: --> so why it is not commented out? + +/* switch( item->giType ) + { + case IT_WEAPON: + if ( ( ps->stats[STAT_WEAPONS] & (1 << item->giTag) ) ) // RPG-X | Marcin | 04/12/2008 + { + return qfalse; + } + return qtrue; + + case IT_AMMO: + if ( ps->ammo[ item->giTag ] >= Max_Ammo[ item->giTag ]) { + return qfalse; // can't hold any more + } + //if ( ps->persistant[PERS_CLASS] != PC_NOCLASS )//FIXME: should be checking g_pModSpecialties->integer != 0 ) + //{ + // if ( ps->persistant[PERS_CLASS] != PC_ACTIONHERO ) + // { + // //only pick it up if you have the weapon + // if ( !(ps->stats[STAT_WEAPONS]&(1<giTag)) ) + // {//don't have the weapon that uses this ammo + // return qfalse; + // } + // } + //} + return qtrue; + + case IT_ARMOR: + // we also clamp armor to the maxhealth for handicapping + if ( ps->stats[STAT_ARMOR] >= ps->stats[STAT_MAX_HEALTH] * 2 ) { + return qfalse; + } + return qtrue; + + case IT_HEALTH: + // small and mega healths will go over the max, otherwise + // don't pick up if already at max + if ( item->quantity == 5 || item->quantity == 100 ) { + if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] * 2 ) { + return qfalse; + } + return qtrue; + } + + if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] ) { + return qfalse; + } + return qtrue; + + case IT_POWERUP: + //if ( ps->persistant[PERS_CLASS] == PC_ACTIONHERO ) + //{ + // if ( item->giTag == PW_LASER ) + // { + // return qfalse; + // } + //} + //else if ( ps->persistant[PERS_CLASS] != PC_NOCLASS )//FIXME: should be checking g_pModSpecialties->integer != 0 ) + //{//in specialty mode, only certain classes can use certain powerups + // //FIXME: breaks bots! + // switch( item->giTag ) + // { + // case PW_QUAD: + // if ( ps->persistant[PERS_CLASS] != PC_HEAVY ) + // { + // return qfalse; + // } + // break; + // case PW_BOLTON: + // if ( ps->persistant[PERS_CLASS] != PC_MEDIC ) + // { + // return qfalse; + // } + // break; + // case PW_FLIGHT: + // if ( ps->persistant[PERS_CLASS] != PC_INFILTRATOR ) + // { + // return qfalse; + // } + // break; + // } + //} + return qtrue; // powerups are always picked up + + case IT_TEAM: // team items, such as flags + // ent->modelindex2 is non-zero on items if they are dropped + // we need to know this because we can pick up our dropped flag (and return it) + // but we can't pick up our flag at base + if (ps->persistant[PERS_TEAM] == TEAM_RED) { + //if (item->giTag == PW_BORG_ADAPT || + // (item->giTag == PW_REDFLAG && ent->modelindex2) || + // (item->giTag == PW_REDFLAG && ps->powerups[PW_BORG_ADAPT])) + // return qtrue; + } else if (ps->persistant[PERS_TEAM] == TEAM_BLUE) { + //if (item->giTag == PW_REDFLAG || + // (item->giTag == PW_BORG_ADAPT && ent->modelindex2) || + // (item->giTag == PW_BORG_ADAPT && ps->powerups[PW_REDFLAG])) + // return qtrue; + } + return qfalse; + + case IT_HOLDABLE: + // can only hold one item at a time + if ( ps->stats[STAT_HOLDABLE_ITEM] ) { + return qfalse; + } + return qtrue; + + case IT_BAD: + Com_Error( ERR_DROP, "BG_CanItemBeGrabbed: IT_BAD" ); + } + + return qfalse; */ +} + +//====================================================================== + +/** +* Evaluates a trajectory. +*/ +void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ) { + float deltaTime; + float phase; + + switch( tr->trType ) { + case TR_STATIONARY: + case TR_INTERPOLATE: + VectorCopy( tr->trBase, result ); + break; + case TR_LINEAR: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + case TR_SINE: + deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; + phase = sin( deltaTime * M_PI * 2 ); + VectorMA( tr->trBase, phase, tr->trDelta, result ); + break; + case TR_LINEAR_STOP: + if ( atTime > tr->trTime + tr->trDuration ) { + atTime = tr->trTime + tr->trDuration; + } + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + if ( deltaTime < 0 ) { + deltaTime = 0; + } + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + case TR_GRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + result[2] -= 0.5 * DEFAULT_GRAVITY * deltaTime * deltaTime; // FIXME: local gravity... + break; + default: + Com_Error( ERR_DROP, "BG_EvaluateTrajectory: unknown trType: %i", tr->trTime ); + break; + } +} + +/** +* Determining velocity at a given time +*/ +void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ) { + float deltaTime; + float phase; + + switch( tr->trType ) { + case TR_STATIONARY: + case TR_INTERPOLATE: + VectorClear( result ); + break; + case TR_LINEAR: + VectorCopy( tr->trDelta, result ); + break; + case TR_SINE: + deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; + phase = cos( deltaTime * M_PI * 2 ); // derivative of sin = cos + phase *= 0.5; + VectorScale( tr->trDelta, phase, result ); + break; + case TR_LINEAR_STOP: + if ( atTime > tr->trTime + tr->trDuration ) { + VectorClear( result ); + return; + } + VectorCopy( tr->trDelta, result ); + break; + case TR_GRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorCopy( tr->trDelta, result ); + result[2] -= DEFAULT_GRAVITY * deltaTime; // FIXME: local gravity... + break; + default: + Com_Error( ERR_DROP, "BG_EvaluateTrajectoryDelta: unknown trType: %i", tr->trTime ); + break; + } +} + +/** +* Handles the sequence numbers +*/ +void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ) { + ps->events[ps->eventSequence & (MAX_PS_EVENTS-1)] = newEvent; + ps->eventParms[ps->eventSequence & (MAX_PS_EVENTS-1)] = eventParm; + ps->eventSequence++; +} + + +/** +* \brief Playerstate to entitystate +* +* This is done after each set of usercmd_t on the server, +* and after local prediction on the client +*/ +void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ) { + int i; + char medicrevive[32]; + int medicrevive_int; + + //RPG-X: RedTechie - Attempted to fix player going invisible now they just dont go invisible (me being picky) a player is never going to notice this + trap_Cvar_VariableStringBuffer( "rpg_medicsrevive", medicrevive, 32 ); + medicrevive_int = atoi(medicrevive); + if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR ) { + s->eType = ET_INVISIBLE; + } else if ( ps->stats[STAT_HEALTH] <= GIB_HEALTH ) { + if(medicrevive_int == 1){ + s->eType = ET_PLAYER; //RPG-X: RedTechie - No gibbing! Before it was s->eType = ET_INVISIBLE; + }else{ + 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 ); + } + + //TiM - Get velocity as well + VectorCopy( ps->velocity, s->pos.trDelta ); + if ( snap ) + { + SnapVector( 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; + + //TiM + s->torsoAnim = ps->stats[TORSOANIM]; + s->legsAnim = ps->stats[LEGSANIM]; + + //s->legsAnim = ps->legsAnim; + //s->torsoAnim = ps->torsoAnim; + + //TiM : Mental note : DON'T FREEAKIN ACCIDENTLY COMMENT THIS OUT AGAIN! IT'S KINDA IMPORTANT!!!!!!!!!!!!!!!! + 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 ( ( !medicrevive_int && ps->stats[STAT_HEALTH] <= 0 ) || + ( medicrevive_int > 0 && ps->stats[STAT_HEALTH] <= 1 ) ) + { //RPG-X: TiM: Bah Red... u gotta account for these flags with ur system + s->eFlags |= EF_DEAD; //or it screws up the model system + } else { + s->eFlags &= ~EF_DEAD; + } + + //========================================================================== + //TiM: ^&$*#^^.... T_T + //Okay it's official. eFlags is buggy. Turns out the Ravensoft programmers were + //wreaking grief with it too. :P + //Although hacky, transposing these flags here is the only way I know to get this data from G to CG. + + //Clamp body (not head) flag + if ( ps->stats[EMOTES] & EMOTE_CLAMP_BODY ) { + s->eFlags |= EF_CLAMP_BODY; + } + else { + s->eFlags &= ~EF_CLAMP_BODY; + } + + //Clamp whole body flags + if ( ps->stats[EMOTES] & EMOTE_CLAMP_ALL ) { + s->eFlags |= EF_CLAMP_ALL; + } + else { + s->eFlags &= ~EF_CLAMP_ALL; + } + + if ( ps->stats[EMOTES] & EMOTE_EYES_SHUT ) + { + s->eFlags |= EF_EYES_SHUT; + //Com_Printf( "Eyes were shutted.\n" ); + } + else { + s->eFlags &= ~EF_EYES_SHUT; + } + + if ( ps->stats[EMOTES] & EMOTE_EYES_PISSED ) { + s->eFlags |= EF_EYES_ANGRY; + } + else { + s->eFlags &= ~EF_EYES_ANGRY; + } + + //========================================================================== + + 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->powerups = 0; + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) + { + if ( ps->powerups[ i ] ) { + s->powerups |= 1 << i; + } + } + + if ( s->powerups & (1 << PW_FLIGHT) || (ps->powerups[PW_EVOSUIT] && ps->gravity == 0 ) ) + { + s->eFlags |= EF_FULL_ROTATE; + } + else + { + s->eFlags &= ~EF_FULL_ROTATE; + } + + //TiM: Extra - Transmit the weapons stats as a flag for the 'equip' command + s->time2 = ps->stats[STAT_WEAPONS]; +} + +#define MAX_ITEMNAMES 45 + +const char *itemnames[MAX_ITEMNAMES] = +{ + +"nothing", +"WEAPON_NULL_HAND", +"WEAPON_TRICORDER", +"WEAPON_PADD", +"WEAPON_COFFEE", +"WEAPON_PHASER", +"WEAPON_COMPRESSIONRIFLE", +"WEAPON_TR116", +"WEAPON_GRENADELAUNCHER", +"WEAPON_QUANTUM", +"WEAPON_DISRUPTOR", +"WEAPON_MEDKIT", +"WEAPON_VOYAGER_HYPO", +"WEAPON_DERMAL_REGEN", +"WEAPON_TOOLKIT", +"WEAPON_NEUTRINO_PROBE", + +"AMMO_COMPRESSIONRIFLE", +"AMMO_IMOD", +"AMMO_SCAVENGERRIFLE", +"AMMO_STASIS", +"AMMO_GRENADELAUNCHER", +"AMMO_DISRUPTOR", +"AMMO_QUANTUM", +"AMMO_DREADNOUGHT", + +"ITEM_ARMOR_SHARD", +"ITEM_ARMOR", +"ITEM_HEAVY_ARMOR", +"ITEM_HYPO_SMALL", +"ITEM_HYPO", + +"HOLDABLE_TRANSPORTER", +"HOLDABLE_MEDKIT", +"HOLDABLE_QUADDAMAGE", +"HOLDABLE_BATTLESUIT", +"HOLDABLE_SPEED", +"HOLDABLE_INVISIBILITY", +"HOLDABLE_REGENERATION", +"HOLDABLE_FLIGHT", +"HOLDABLE_REDFLAG", +"HOLDABLE_BLUEFLAG", +"HOLDABLE_DETPACK", +"ITEM_SEEKER", +"HOLDABLE_SHIELD", +"HOLOGRAPHIC_DECOY", // decoy temp + +"WEAPON_TR116", + +NULL +}; + + +#define MAX_ITEMNAMEFILE 10000 //5000 har har +char itemNameBuffer[MAX_ITEMNAMEFILE]; + +/** +* Explains itself. +*/ +void BG_ParseItemsText(char *buff) +{ + char *token; + char *buffer; + int i,len; + + COM_BeginParseSession(); + + buffer = buff; + while ( buffer ) + { + token = COM_ParseExt( &buffer, qtrue ); + + i=0; + while (itemnames[i]) + { + if (Q_stricmp(token, itemnames[i])==0) + { + token = COM_ParseExt( &buffer, qtrue ); + if (token[0]) + { + len = strlen(token); + if (len) + { + bg_itemlist[i].pickup_name = (buffer - (len + 1)); // The +1 is to get rid of the " at the beginning of the sting. + *(buffer - 1) = '\0'; // Place an string end where is belongs. + } + } + + break; + } + i++; + } + + } +} + +/* +* Creates a filename with an extension based on the value in g_language +*/ +void BG_LanguageFilename(char *baseName,char *baseExtension,char *finalName) +{ + char language[32]; + fileHandle_t file; + + trap_Cvar_VariableStringBuffer( "g_language", language, 32 ); + + // If it's English then no extension + if (language[0]=='\0' || Q_stricmp ("ENGLISH",language)==0) + { + Com_sprintf(finalName,MAX_QPATH,"%s.%s",baseName,baseExtension); + } + else + { + Com_sprintf(finalName,MAX_QPATH,"%s_%s.%s",baseName,language,baseExtension); + + //Attempt to load the file + trap_FS_FOpenFile( finalName, &file, FS_READ ); + + if ( file == 0 ) // This extension doesn't exist, go English. + { + Com_sprintf(finalName,MAX_QPATH,"%s.%s",baseName,baseExtension); //the caller will give the error if this isn't there + } + else + { + trap_FS_FCloseFile( file ); + } + } +} + +/** +* Loads item names. +*/ +void BG_LoadItemNames(void) +{ + char fileName[MAX_QPATH]; + int len; + fileHandle_t f; + + BG_LanguageFilename("ext_data/mp_itemnames","dat",fileName); + + len = trap_FS_FOpenFile( fileName, &f, FS_READ ); + + if ( !f ) + { + Com_Printf( S_COLOR_RED "BG_LoadItemNames : MP_ITEMNAMES.DAT file not found!\n"); + return; + } + + if ( len > MAX_ITEMNAMEFILE ) + { + Com_Printf( S_COLOR_RED "BG_LoadItemNames : MP_ITEMNAMES.DAT too big!\n"); + return; + } + + // initialise the data area + memset(itemNameBuffer, 0, sizeof(itemNameBuffer)); + + trap_FS_Read( itemNameBuffer, len, f ); + + trap_FS_FCloseFile( f ); + + BG_ParseItemsText(itemNameBuffer); + +} + +/** +* Read a configuration file to get the sex +* models/players_rpgx/munro/animation.cfg +*/ +#ifdef Q3_VM +static gender_t G_ParseAnimationFileSex( const char *filename) { + char *text_p; + int len; + char *token; + char text[20000]; + fileHandle_t f; + char animfile[MAX_QPATH]; + + //strcpy(animfile, filename); + Q_strncpyz(animfile, filename, sizeof(animfile)); + len = strlen(animfile); + strcpy(&animfile[len-strlen("groups.cfg")], "animation.cfg"); + + // load the file + len = trap_FS_FOpenFile( animfile, &f, FS_READ ); + if ( len <= 0 ) { + return GENDER_NEUTER; + } + if ( len >= sizeof( text ) - 1 ) { + Com_Printf( "File %s too long\n", animfile ); + trap_FS_FCloseFile( f ); + return GENDER_NEUTER; + } + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + // read optional parameters + while ( 1 ) { + token = COM_Parse( &text_p ); + if ( !token[0] ) { + break; + } + if ( !Q_stricmp( token, "sex" ) ) { + token = COM_Parse( &text_p ); + if ( !token[0] ) { + break; + } + if ( token[0] == 'f' || token[0] == 'F' ) { + return GENDER_FEMALE; + } else if ( token[0] == 'n' || token[0] == 'N' ) { + return GENDER_NEUTER; + } else { + return GENDER_MALE; + } + } + } + return GENDER_MALE; +} +#else +static gender_t G_ParseAnimationFileSex( const char *filename) { + char *text_p; + int len; + char *token; + char *text; + fileHandle_t f; + char animfile[MAX_QPATH]; + + //strcpy(animfile, filename); + Q_strncpyz(animfile, filename, sizeof(animfile)); + len = strlen(animfile); + strcpy(&animfile[len-strlen("groups.cfg")], "animation.cfg"); + + // load the file + len = trap_FS_FOpenFile( animfile, &f, FS_READ ); + if ( len <= 0 ) { + return GENDER_NEUTER; + } + + text = (char *)malloc(20000 * sizeof(char)); + if(!text) { + trap_FS_FCloseFile(f); + Com_Printf( "Was unable to allocate %i bytes.\n", 20000 * sizeof(char) ); + return GENDER_NEUTER; + } + + if ( len >= 20000 - 1 ) { + Com_Printf( "File %s too long\n", animfile ); + trap_FS_FCloseFile( f ); + return GENDER_NEUTER; + } + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + // read optional parameters + while ( 1 ) { + token = COM_Parse( &text_p ); + if ( !token[0] ) { + break; + } + if ( !Q_stricmp( token, "sex" ) ) { + token = COM_Parse( &text_p ); + if ( !token[0] ) { + break; + } + if ( token[0] == 'f' || token[0] == 'F' ) { + free(text); + return GENDER_FEMALE; + } else if ( token[0] == 'n' || token[0] == 'N' ) { + free(text); + return GENDER_NEUTER; + } else { + free(text); + return GENDER_MALE; + } + } + } + free(text); + return GENDER_MALE; +} +#endif + +/** +* Registers an item. +*/ +#define MAX_GROUP_FILE_SIZE 5000 +char* BG_RegisterRace( const char *name ) { + char *text_p; + char *token; + int len; + fileHandle_t f; + char text[MAX_GROUP_FILE_SIZE]; + gender_t theSex; + + memset (races, 0, sizeof(races)); + memset (text, 0, sizeof(text)); + + // load and parse the skin file + len = trap_FS_FOpenFile( name, &f, FS_READ ); + if ( !f ) { + // if we didn't get a races file, use an empty one. + Com_sprintf(races, sizeof(races), "unknown"); + return races; + } + if ( len >= sizeof( text ) - 1) + { + Com_Printf( S_COLOR_RED "file too large: %s is %i, max allowed is %i", name, len, sizeof( text ) ); + trap_FS_FCloseFile( f ); + return races; + } + + trap_FS_Read( text, len, f ); + trap_FS_FCloseFile( f ); + + theSex = G_ParseAnimationFileSex(name); + if (theSex == GENDER_MALE) { + strcat(races, "Male,"); + } else if (theSex == GENDER_FEMALE) { + strcat(races, "Female,"); + } + + text_p = text; + while ( *text_p ) { + // get surface name + token = COM_Parse( &text_p ); + + if ( !token[0] ) { + break; + } + + // if we about to break the races size list then dump us out + if (strlen(races) + strlen(token) > 256) { + break; + } + + // add it into the race list + strcat(races, token); + // put a comma between the names + strcat(races, ","); + + if ( *text_p == ',' ) { + text_p++; + } + + if (!Q_stricmp ("borg", token) ) { + if (theSex == GENDER_MALE) { + // add it into the race list + strcat(races, "BorgMale,"); + } else if (theSex == GENDER_FEMALE) { + strcat(races, "BorgFemale,"); + } else { + } + } + + } + + // just in case + if (!races[0]) + { + Com_sprintf(races, sizeof(races), "unknown"); + } + else + { //lose the last comma + races[strlen(races)-1] = 0; + } + + return races; +} + +/** +* Parses the rank names. +*/ +#ifdef Q3_VM +qboolean BG_ParseRankNames( char* fileName, rankNames_t rankNames[] ) { + fileHandle_t f; + int file_len; + char charText[20000]; + char* textPtr; + char* token; + int i = 0; + + file_len = trap_FS_FOpenFile( fileName, &f, FS_READ ); + + if ( file_len<= 0 ) { + return qfalse; + } + + if ( file_len >= ( sizeof(charText) - 1) ) { + Com_Printf( S_COLOR_RED "File length of %s is too long.\n", fileName ); + return qfalse; + } + + memset( &charText, 0, sizeof( charText ) ); + memset( rankNames, 0, sizeof( rankNames ) ); + + trap_FS_Read( charText, file_len, f ); + + charText[file_len] = 0; + + trap_FS_FCloseFile( f ); + + COM_BeginParseSession(); + + textPtr = charText; + + token = COM_Parse( &textPtr ); + + if ( !token[0] ) { + Com_Printf( S_COLOR_RED "No data found in buffer: %s\n", fileName ); + return qfalse; + } + + if ( Q_stricmpn( token, "{", 1 ) ) { + Com_Printf( S_COLOR_RED "No beginning { found in %s\n", fileName ); + return qfalse; + } + + //Parse out the default cell. Default has no names anyway, + //but in case a n00bie modder put names in anyway. + SkipBracedSection( &textPtr ); + + while( 1 ) { + //lastPtr = textPtr; + token = COM_Parse( &textPtr ); + if( !token[0] ) { + break; + } + + if ( i >= MAX_RANKS ) { + break; + } + + //If we hit an open brace (ie, assuming we hit the start of a new rank cell) + if ( !Q_stricmpn( token, "{", 1 ) ) { + while ( 1 ) { + token = COM_Parse( &textPtr ); + if( !token[0] ) { + break; + } + + //We hit a MenuTexture entry, since this uses { symbols, we'll skip these to stop errors. + if ( !Q_stricmpn( token, "MenuTexture", 11 ) ) { + SkipRestOfLine( &textPtr ); + continue; + } + + if ( !Q_stricmpn( token, "ConsoleName", 11) ) { + if ( COM_ParseString( &textPtr, &token ) ) { + continue; + } + + Q_strncpyz( rankNames[i].consoleName, token, sizeof( rankNames[i].consoleName ) ); + + continue; + } + else if ( !Q_stricmpn( token, "FormalName", 10) ) { + if ( COM_ParseString( &textPtr, &token ) ) { + continue; + } + + Q_strncpyz( rankNames[i].formalName, token, sizeof( rankNames[i].formalName ) ); + + continue; + } + //We hit the end of the cell. + else if ( !Q_stricmpn( token, "}", 1 ) ) { + break; + } + } + + //Error check. If we didn't get both a formal and console name, pwn the caller. ;P + if ( !rankNames[i].consoleName[0] || !rankNames[i].formalName[0] ) { + Com_Printf( S_COLOR_RED "One or more rank names were not found in rank#: %i\n", i ); + return qfalse; + } + else { + i++; + } + } + } + return qtrue; +} +#else +qboolean BG_ParseRankNames( char* fileName, rankNames_t rankNames[] ) { + fileHandle_t f; + int file_len; + char *charText; + char* textPtr; + char* token; + int i = 0; + + file_len = trap_FS_FOpenFile( fileName, &f, FS_READ ); + + if ( file_len<= 0 ) { + return qfalse; + } + + charText = (char *)malloc(20000 * sizeof(char)); + if(!charText) { + Com_Printf( S_COLOR_RED "Was unable to allocate %i bytes.\n", 20000 * sizeof(char) ); + trap_FS_FCloseFile(f); + return qfalse; + } + + if ( file_len >= ( 20000 - 1) ) { + Com_Printf( S_COLOR_RED "File length of %s is too long.\n", fileName ); + trap_FS_FCloseFile(f); + free(charText); + return qfalse; + } + + memset( rankNames, 0, sizeof( rankNames ) ); + + trap_FS_Read( charText, file_len, f ); + + charText[file_len] = 0; + + trap_FS_FCloseFile( f ); + + COM_BeginParseSession(); + + textPtr = charText; + + token = COM_Parse( &textPtr ); + + if ( !token[0] ) { + Com_Printf( S_COLOR_RED "No data found in buffer: %s\n", fileName ); + free(charText); + return qfalse; + } + + if ( Q_stricmpn( token, "{", 1 ) ) { + Com_Printf( S_COLOR_RED "No beginning { found in %s\n", fileName ); + free(charText); + return qfalse; + } + + //Parse out the default cell. Default has no names anyway, + //but in case a n00bie modder put names in anyway. + SkipBracedSection( &textPtr ); + + while( 1 ) { + //lastPtr = textPtr; + token = COM_Parse( &textPtr ); + if( !token[0] ) { + break; + } + + if ( i >= MAX_RANKS ) { + break; + } + + //If we hit an open brace (ie, assuming we hit the start of a new rank cell) + if ( !Q_stricmpn( token, "{", 1 ) ) { + while ( 1 ) { + token = COM_Parse( &textPtr ); + if( !token[0] ) { + break; + } + + //We hit a MenuTexture entry, since this uses { symbols, we'll skip these to stop errors. + if ( !Q_stricmpn( token, "MenuTexture", 11 ) ) { + SkipRestOfLine( &textPtr ); + continue; + } + + if ( !Q_stricmpn( token, "ConsoleName", 11) ) { + if ( COM_ParseString( &textPtr, &token ) ) { + continue; + } + + Q_strncpyz( rankNames[i].consoleName, token, sizeof( rankNames[i].consoleName ) ); + + continue; + } + else if ( !Q_stricmpn( token, "FormalName", 10) ) { + if ( COM_ParseString( &textPtr, &token ) ) { + continue; + } + + Q_strncpyz( rankNames[i].formalName, token, sizeof( rankNames[i].formalName ) ); + + continue; + } + //We hit the end of the cell. + else if ( !Q_stricmpn( token, "}", 1 ) ) { + break; + } + } + + //Error check. If we didn't get both a formal and console name, pwn the caller. ;P + if ( !rankNames[i].consoleName[0] || !rankNames[i].formalName[0] ) { + Com_Printf( S_COLOR_RED "One or more rank names were not found in rank#: %i\n", i ); + return qfalse; + } + else { + i++; + } + } + } + free(charText); + return qtrue; +} +#endif + +/* +=========== +NextWordEndsHere +=========== +*/ +char *NextWordEndsHere(char *p) +{ + if (*p != ' ') { + return p; + } + + while (*p && *p == ' ') { // first pass + ++p; + } + + while (*p && *p != ' ') { // second pass + ++p; + } + + return p; +} + +/* +=========== +EndWord +=========== +Returns a pointer to the position of the next space, null, or newline. +*/ +char *EndWord(char *pos) +{ + while (!*pos && *pos != ' ' && *pos != '\n') { + ++pos; + } + + return pos; +} diff --git a/game/bg_oums.c b/game/bg_oums.c new file mode 100644 index 0000000..139597f --- /dev/null +++ b/game/bg_oums.c @@ -0,0 +1,2 @@ + + diff --git a/game/bg_oums.h b/game/bg_oums.h new file mode 100644 index 0000000..2529dfb --- /dev/null +++ b/game/bg_oums.h @@ -0,0 +1,4 @@ +/* +bg_OUMS.h +ONLINE USER MANAGEMENT SYSTEM +*/ diff --git a/game/bg_panimate.c b/game/bg_panimate.c new file mode 100644 index 0000000..7ebc184 --- /dev/null +++ b/game/bg_panimate.c @@ -0,0 +1,12 @@ +//=========================================================== +// Name: bg_panimate.c +// Developer: Timothy 'TiM' Oliver +// Date: 16/2/2006 +// Function: Global character animation functions. Utilised +// in CG and G for animation loading + handling. +// Based off of logic from EF SP. +//=========================================================== + +#include "q_shared.h" +#include "bg_public.h" + diff --git a/game/bg_pmove.c b/game/bg_pmove.c new file mode 100644 index 0000000..4c07004 --- /dev/null +++ b/game/bg_pmove.c @@ -0,0 +1,3848 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// 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; + +// movement parameters +const float pm_stopspeed = 100; +const float pm_duckScale = 0.50; +const float pm_swimScale = 0.50; +const float pm_ladderScale = 0.7f; + +const float pm_accelerate = 10; +const float pm_airaccelerate = 1; +const float pm_wateraccelerate = 4; +const float pm_flyaccelerate = 8; + +const float pm_friction = 6; +const float pm_waterfriction = 1; +const float pm_flightfriction = 3; +const float pm_evosuitfriction = 0.25; //RPG-X | Phenix | 8/8/2004 + +int c_pmove = 0; + +#define PHASER_RECHARGE_TIME 100 + +//RPG-X | Marcin | Big hack but it appears to work | 06/12/2008 +#ifdef QAGAME +extern vmCvar_t rpg_rifleDelay; +extern vmCvar_t rpg_disruptorDelay; +extern vmCvar_t rpg_photonDelay; +extern vmCvar_t rpg_altPhotonDelay; +extern vmCvar_t rpg_TR116Delay; +extern vmCvar_t rpg_altTricorderDelay; +#define RIFLE_DELAY rpg_rifleDelay.integer +#define DISRUPTOR_DELAY rpg_disruptorDelay.integer +#define PHOTON_DELAY rpg_photonDelay.integer +#define ALT_PHOTON_DELAY rpg_altPhotonDelay.integer +#define TR116_DELAY rpg_TR116Delay.integer +#define ALT_TRICORDER_DELAY rpg_altTricorderDelay.integer +#else +#define RIFLE_DELAY 250 +#define DISRUPTOR_DELAY 700 +#define PHOTON_DELAY 1600 +#define ALT_PHOTON_DELAY 1600 +#define TR116_DELAY 500 +#define ALT_TRICORDER_DELAY 1000 +#endif + +//qboolean BG_BorgTransporting( playerState_t *ps ) +//{ +// if ( ps->persistant[PERS_CLASS] == PC_BORG && bg_itemlist[ps->stats[STAT_HOLDABLE_ITEM]].giTag == HI_TRANSPORTER && ps->stats[STAT_USEABLE_PLACED] == 2 ) +// {//A player who has an item and it's set to 2 - meaning flight +// return qtrue; +// } +// return qfalse; +//} + +/*typedef enum { + ANIM_CROUCH, + ANIM_DOCROUCH, + ANIM_UNCROUCH, + ANIM_STAND, + ANIM_FIRE, + ANIM_JUMP, + ANIM_JUMPB, + ANIM_RUN, + ANIM_RUNB, + ANIM_WALK, + ANIM_WALKB, + ANIM_CROUCHWALK, + ANIM_CROUCHWALKB, + ANIM_SWIM, + ANIM_TURN, + ANIM_FLY, +} animList_t;*/ + +//TiM - Copied from the UI module. +//My aim here is to adapt the checks code here so the function can be used both for +//ingame animation, as well as UI animation + +// RPG-X UI Required Ones +#define ANIM_IDLE 0 +#define ANIM_RUN 1 +#define ANIM_WALK 2 +#define ANIM_BACK 3 +#define ANIM_JUMP 4 +#define ANIM_CROUCH 5 +#define ANIM_ATTACK 22 +//Ingame required ones +#define ANIM_JUMPB 6 +#define ANIM_RUNB 7 +#define ANIM_WALKB 8 +#define ANIM_CROUCHWALK 9 +#define ANIM_CROUCHWALKB 10 +#define ANIM_SWIM 11 +#define ANIM_FLY 12 +#define ANIM_TURN 13 + +/** +* Checks if the player holding a two handed weapon. +*/ +qboolean PM_Holding2HandedWeapon ( void ) { + switch (pm->ps->weapon) { + case WP_TR116: + case WP_GRENADE_LAUNCHER: + case WP_QUANTUM_BURST: + case WP_COMPRESSION_RIFLE: + return qtrue; + } + return qfalse; +} + +/** +* Checks if the player is crouching. +*/ +qboolean PM_PlayerCrouching ( int legsAnim ) { + //switch( pm->ps->legsAnim ) { + switch( ( legsAnim & ~ANIM_TOGGLEBIT) ) { + //case BOTH_CROUCH1: + case BOTH_CROUCH1IDLE: + case BOTH_CROUCH1WALK: + case BOTH_CROUCH2IDLE: + //case BOTH_CROUCH2TOSTAND1: + return qtrue; + } + return qfalse; +} + +/** +* Check if player is idling. +*/ +qboolean PM_PlayerIdling ( int torsoAnim, int legsAnim ) { + //switch( pm->ps->legsAnim ) { + //TiM : Cool hacky way to make sure both upper and lower anims are the same + switch( ( legsAnim & ~ANIM_TOGGLEBIT) ) //+ ( torsoAnim & ~ANIM_TOGGLEBIT)) >> 1 + { + case BOTH_STAND1: + case BOTH_STAND2: + case BOTH_STAND3: + case BOTH_STAND4: + case BOTH_CROWDLOOK3: + { + switch ( ( torsoAnim & ~ANIM_TOGGLEBIT) ) + { + case BOTH_STAND1: + case BOTH_STAND2: + case BOTH_STAND3: + case BOTH_STAND4: + case BOTH_CROWDLOOK3: + case TORSO_TRICORDER1: + case TORSO_HYPOSPRAY1: + case TORSO_HYPO1: + case TORSO_DROPWEAP1: + case TORSO_RAISEWEAP1: + case TORSO_PADD1: + case TORSO_COFFEE: + return qtrue; + } + } + } + return qfalse; +} + + +/** +* \brief Checks if player is holding a loppable weapon. +* +* A weapon that can have its +* firing animation looped, +* like the PADD or tricorder, etc +*/ +qboolean PM_HoldingLoopableWeapon ( void ) { + switch (pm->ps->weapon) { + case WP_DERMAL_REGEN: + case WP_TRICORDER: + case WP_PADD: + case WP_MEDKIT: + case WP_COFFEE: + return qtrue; + } + return qfalse; +} + +/** +* \brief Checks if player is holding a spillable weapon. +* +* Player is holding a weapon that +* shouldn't let players do the +* 'slowing down' anim +*/ +qboolean PM_HoldingSpillableWeapon( void ) { + switch ( pm->ps->weapon ) { + case WP_COFFEE: + case WP_COMPRESSION_RIFLE: + case WP_QUANTUM_BURST: + case WP_GRENADE_LAUNCHER: + case WP_TR116: + return qtrue; + } + return qfalse; +} + + +/** +* Check to see if the player is moving at all +*/ +qboolean PM_PlayerWalking( int anim ) +{ + switch( anim & ~ANIM_TOGGLEBIT ) + { + case BOTH_WALK1: + case BOTH_WALK2: + case BOTH_WALK3: + case BOTH_WALK4: + case BOTH_WALK7: + case LEGS_WALKBACK1: + return qtrue; + } + + return qfalse; +} + +/** +* Check to see if the player is running +*/ +qboolean PM_PlayerRunning( int anim ) +{ + switch( anim & ~ANIM_TOGGLEBIT ) + { + case BOTH_RUN1: + case BOTH_RUN2: + case LEGS_RUNBACK2: + return qtrue; + } + + return qfalse; +} + +/** +* Check to see if the player is moving while crouching +*/ +qboolean PM_PlayerCrouchWalking( int anim ) +{ + switch( anim & ~ANIM_TOGGLEBIT ) + { + case BOTH_CROUCH1WALK: + return qtrue; + } + + return qfalse; +} + +/** +* TiM: An index is defined, and depending +* on which weapon is active, a specific +* animation is returned. +* I could have used pm->ps->weapon instead +* of manually defining it as an paramter, +* but I'm going to use this in the UI module, +* which is out of pm's scope. +*/ +int PM_GetAnim ( int anim, int weapon, qboolean injured, qboolean upper ) +{ + playerState_t *ps = pm->ps; + // Called when player is in idle crouching + switch ( anim ) { + case ANIM_CROUCH: + //2 handed weapon - "heavy" + switch (weapon) { + case WP_TR116: + case WP_GRENADE_LAUNCHER: + case WP_QUANTUM_BURST: + if ( ps->pm_flags & ANIM_ALERT2 && upper ) + return TORSO_WEAPONREADY2; + else if (upper) + return BOTH_STAND2; + else + return LEGS_KNEEL1; + break; + //2 handed weapon - "light" + case WP_COMPRESSION_RIFLE: + //case WP_TR116: + if ( ps->pm_flags & ANIM_ALERT && upper ) + return BOTH_STAND2; + else if (upper) + return TORSO_WEAPONREADY2; + else + return LEGS_KNEEL1; + break; + //1 handed weapon - "phaser" + case WP_PHASER: + case WP_DISRUPTOR: + if ( upper ) + return TORSO_WEAPONPOSE1; + else + return BOTH_CROUCH1IDLE; + break; + case WP_COFFEE: + if (upper) + return TORSO_COFFEE; + //break; + //Generic tools - "everything else" + default: + return BOTH_CROUCH2IDLE; + break; + } + break; + + //Called when player is in idle standing + case ANIM_IDLE: + //2 handed weapon - "heavy" + switch (weapon) { + //case WP_TR116: + case WP_GRENADE_LAUNCHER: + case WP_QUANTUM_BURST: + case WP_TR116: + if (injured) + return BOTH_INJURED4; + else + { + if ( ps->pm_flags & ANIM_ALERT ) + return BOTH_STAND2; + else if ( ps->pm_flags & ANIM_ALERT2 ) + { + if ( upper ) + return TORSO_WEAPONREADY2; + else + return BOTH_STAND2; + } + else + return BOTH_STAND4; + } + break; + //2 handed weapon - "light" + case WP_COMPRESSION_RIFLE: + if (injured) + return BOTH_INJURED4; + else + { + if ( ps->pm_flags & ANIM_ALERT ) + return BOTH_STAND2; + else if ( ps->pm_flags & ANIM_ALERT2 ) + { + if ( upper ) + return TORSO_WEAPONREADY2; + else + return BOTH_STAND2; + } + else + return BOTH_STAND4; + } + break; + //1 handed weapon - "phaser" + case WP_PHASER: + case WP_DISRUPTOR: + if (injured) + return BOTH_INJURED4; + else { + if ( ps->pm_flags & ANIM_ALERT && upper ) + return TORSO_WEAPONIDLE1; + else if ( ps->pm_flags & ANIM_ALERT2 && upper ) + return TORSO_WEAPONREADY1; + else + return BOTH_STAND1; + } + break; + //Generic tools - "everything else" + case WP_COFFEE: + if (upper) + return TORSO_COFFEE; + else + return BOTH_STAND1; + break; + default: + if (injured) + return BOTH_INJURED4; + else + return BOTH_STAND1; + break; + } + break; + + //Called when player fires their weapon + case ANIM_ATTACK: + //2 handed weapon - "heavy" + switch (weapon) { + //case WP_TR116: + case WP_GRENADE_LAUNCHER: + case WP_QUANTUM_BURST: + case WP_TR116: + if ( ps->pm_flags & ANIM_ALERT2 ) + return BOTH_ATTACK2; + else + return BOTH_ATTACK3; + break; + //2 handed weapon - "light" + case WP_COMPRESSION_RIFLE: + if ( ps->pm_flags & ANIM_ALERT2 ) + return BOTH_ATTACK2; + else + { + if (upper) + return BOTH_ATTACK3; + else + return BOTH_ATTACK3; + } + break; + //1 handed weapon - "phaser" + case WP_PHASER: + case WP_DISRUPTOR: + case WP_HYPERSPANNER: + case WP_DERMAL_REGEN: + if (upper) + return TORSO_WEAPONREADY1; + else + return BOTH_STAND1; + break; + //Other Tools "padd" + case WP_PADD: + if (upper) + return TORSO_PADD1; + else + return BOTH_STAND1; + break; + //Other Tools "tricorder" + case WP_TRICORDER: + if (upper) + { + if ( !pm->medic ) + return TORSO_TRICORDER1; + else + return TORSO_MEDICORDER1; + } + else + return BOTH_STAND1; + break; + //Other: "Medkit" + case WP_MEDKIT: + if (upper) + return TORSO_ACTIVATEMEDKIT1; + else + return BOTH_STAND1; + break; + //Other: "Hypo + case WP_VOYAGER_HYPO: + if (upper) + //return TORSO_HYPOSPRAY1; + return TORSO_HYPO1; + else + return BOTH_STAND1; + //Other: "Toolkit" + /*case WP_TOOLKIT: + //Return nothing. + //A bit hacky, but the engine accepts it :P + break;*/ + //Other Tools "everything else" + /*case WP_NULL_HAND: + switch(rand()%13) + { + case 0: return TORSO_HANDGESTURE1; + case 1: return TORSO_HANDGESTURE2; + case 2: return TORSO_HANDGESTURE3; + case 3: return TORSO_HANDGESTURE4; + case 4: //PM_StartTorsoAnim( TORSO_HANDGESTURE5 ); break; + case 5: return TORSO_HANDGESTURE6; + case 6: return TORSO_HANDGESTURE7; + case 7: return TORSO_HANDGESTURE8; + case 8: return TORSO_HANDGESTURE9; + case 9: return TORSO_HANDGESTURE10; + case 10: return TORSO_HANDGESTURE11; + case 11: return TORSO_HANDGESTURE12; + case 12: return TORSO_HANDGESTURE13; + } + break;*/ + case WP_COFFEE: + if (upper) + return TORSO_COFFEE; + //break; + default: + if (upper) + return TORSO_WEAPONREADY1; + else + return BOTH_STAND1; + break; + } + break; + + //When the player jumps + case ANIM_JUMP: + return BOTH_JUMP1; + //Wen the player jumps backwards + case ANIM_JUMPB: + return BOTH_JUMPBACK1; + + //When the player runs + case ANIM_RUN: + if (injured) { + return BOTH_RUNINJURED1; + } + + //2 handed weapons + switch (weapon) { + //case WP_TR116: + case WP_GRENADE_LAUNCHER: + case WP_QUANTUM_BURST: + case WP_COMPRESSION_RIFLE: + case WP_TR116: + if (upper) + return BOTH_RUN2; + else + return BOTH_RUN1; + break; + case WP_COFFEE: + if (upper) + return TORSO_COFFEE; + //break; + //EVERYTHING ELSE + default: + return BOTH_RUN1; + } + break; + + //When the player runs back + case ANIM_RUNB: + //2 handed weapons + switch (weapon) { + //case WP_TR116: + case WP_GRENADE_LAUNCHER: + case WP_QUANTUM_BURST: + case WP_COMPRESSION_RIFLE: + case WP_TR116: + if (upper) + return BOTH_WALK2; + else + if ( injured ) + return LEGS_WALKBACK1; + else + return LEGS_RUNBACK2; + break; + //EVERYTHING ELSE + case WP_COFFEE: + if (upper) + return TORSO_COFFEE; + //break; + default: + if (upper) + return BOTH_WALK1; + else + if ( injured ) + return LEGS_WALKBACK1; + else + return LEGS_RUNBACK2; + break; + } + break; + + //When the player walks + case ANIM_WALK: + if ( ps->legsTimer > 0 && bg_emoteList[ps->legsTimer].enumName == BOTH_STAND3 ) + return BOTH_WALK3; + + //2 handed weapons + switch (weapon) { + //case WP_TR116: + case WP_GRENADE_LAUNCHER: + case WP_QUANTUM_BURST: + case WP_COMPRESSION_RIFLE: + case WP_TR116: + if ( ps->pm_flags & ANIM_ALERT ) + return BOTH_WALK2; + else if ( ps->pm_flags & ANIM_ALERT2 ) + { + if ( upper ) + return TORSO_WEAPONREADY2; + else + return BOTH_WALK2; + } + else + return BOTH_WALK4; + break; + //Other Tools "everything else" + case WP_COFFEE: + if (upper) + return TORSO_COFFEE; + case WP_PHASER: + case WP_DISRUPTOR: + if ( ps->pm_flags & ANIM_ALERT ) + { + if ( upper ) + return TORSO_WEAPONIDLE1; + } + else if ( ps->pm_flags & ANIM_ALERT2 ) + { + if ( upper ) + return TORSO_WEAPONREADY1; + } + + default: + return BOTH_WALK1; + break; + } + break; + + //When the player walks baaaack + case ANIM_WALKB: + //2 handed weapons + switch (weapon) { + //case WP_TR116: + case WP_GRENADE_LAUNCHER: + case WP_QUANTUM_BURST: + case WP_COMPRESSION_RIFLE: + case WP_TR116: + if ( ps->pm_flags & ANIM_ALERT ) + { + if ( upper ) + return BOTH_WALK2; + else + return LEGS_WALKBACK1; + } + else if ( ps->pm_flags & ANIM_ALERT2 ) + { + if ( upper ) + return TORSO_WEAPONREADY2; + else + return LEGS_WALKBACK1; + } + else + { + if ( upper ) + return BOTH_WALK4; + else + return LEGS_WALKBACK1; + } + break; + case WP_COFFEE: + if (upper) + return TORSO_COFFEE; + //break; + case WP_PHASER: + case WP_DISRUPTOR: + if ( ps->pm_flags & ANIM_ALERT && upper) + return TORSO_WEAPONIDLE1; + else if ( ps->pm_flags & ANIM_ALERT2 && upper ) + return TORSO_WEAPONREADY1; + + //Other Tools "everything else" + default: + if ( upper ) + return BOTH_WALK1; + else + return LEGS_WALKBACK1; + break; + } + break; + + //When the player crouch walks + case ANIM_CROUCHWALK: + //2 handed weapons + switch (weapon) { + //case WP_TR116: + case WP_COMPRESSION_RIFLE: + case WP_TR116: + if ( upper ) + return TORSO_WEAPONREADY2; + else + return BOTH_CROUCH1WALK; + break; + case WP_GRENADE_LAUNCHER: + case WP_QUANTUM_BURST: + if ( ps->pm_flags & ANIM_ALERT2 && upper ) + return TORSO_WEAPONREADY2; + else if ( upper ) + return BOTH_WALK2; + else + return BOTH_CROUCH1WALK; + break; + case WP_COFFEE: + if (upper) + return TORSO_COFFEE; + //break; + case WP_PHASER: + case WP_DISRUPTOR: + if ( ps->pm_flags & ANIM_ALERT && upper ) + return TORSO_WEAPONIDLE1; + else if ( ps->pm_flags & ANIM_ALERT2 && upper ) + return TORSO_WEAPONREADY1; + //Other Tools "everything else" + default: + return BOTH_CROUCH1WALK; + break; + } + break; + + //When the player crouch walks bak + case ANIM_CROUCHWALKB: + //2 handed weapons + switch (weapon) { + //case WP_TR116: + case WP_GRENADE_LAUNCHER: + case WP_QUANTUM_BURST: + case WP_COMPRESSION_RIFLE: + case WP_TR116: + if ( ps->pm_flags & ANIM_ALERT2 ) + return TORSO_WEAPONREADY2; + else if ( upper ) + return BOTH_WALK2; + else + return BOTH_CROUCH1WALK; + break; + case WP_COFFEE: + if (upper) + return TORSO_COFFEE; + //break; + case WP_PHASER: + case WP_DISRUPTOR: + if ( ps->pm_flags & ANIM_ALERT && upper ) + return TORSO_WEAPONIDLE1; + else if ( ps->pm_flags & ANIM_ALERT2 && upper ) + return TORSO_WEAPONREADY1; + + //Other Tools "everything else" + default: + return BOTH_CROUCH1WALK; + break; + } + break; + + case ANIM_SWIM: + if ( !upper ) { + if ( pm->cmd.forwardmove + || pm->cmd.rightmove + || pm->cmd.upmove ) + { + return LEGS_SWIM; + } + } + + return BOTH_FLOAT1; + + /*if ( ps->velocity[2] >= 0 ) { + return BOTH_FLOAT1; + } + else { + return BOTH_FLOAT2; + }*/ + case ANIM_FLY: + return BOTH_FLOAT1; + } + + return BOTH_STAND1; +} + + + +/** +* Adds a predictable event to playerstate +*/ +void PM_AddEvent( int newEvent ) { + BG_AddPredictableEventToPlayerstate( newEvent, 0, pm->ps ); +} + +/* +============== +PM_Use + +Generates a use event +============== +*/ +#define USE_DELAY 2000 + +/** +* Generates a use event +*/ +void PM_Use( void ) +{ + playerState_t *ps = pm->ps; + + if ( ps->useTime > 0 ) + ps->useTime -= 100;//pm->cmd.msec; + + if ( ps->useTime > 0 ) { + return; + } + + if ( ! (pm->cmd.buttons & BUTTON_USE ) ) + { + pm->useEvent = 0; + ps->useTime = 0; + //PM_StartTorsoAnim( BOTH_CONSOLE1 ); + + return; + } + + pm->useEvent = EV_USE; + ps->useTime = USE_DELAY; +} + +/* +=============== +PM_AddTouchEnt +=============== +*/ +/** +* Adds a touchEnt event. +*/ +void PM_AddTouchEnt( int entityNum ) { + int i; + + if ( entityNum == ENTITYNUM_WORLD ) { + return; + } + if ( pm->numtouch == MAXTOUCH ) { + return; + } + + // see if it is already added + for ( i = 0 ; i < pm->numtouch ; i++ ) { + if ( pm->touchents[ i ] == entityNum ) { + return; + } + } + + // add it + pm->touchents[pm->numtouch] = entityNum; + pm->numtouch++; +} + +/** +* Start torso animation +*/ + +static void PM_StartTorsoAnim( int anim, qboolean overrideEmotes ) { + playerState_t *ps = pm->ps; + if ( ps->stats[EMOTES] & EMOTE_UPPER && !overrideEmotes ) { + return; + } + + if ( ps->pm_type >= PM_DEAD && + anim != BOTH_FALLDEATH1INAIR && + anim != BOTH_FALLDEATH1LAND ) + { //TiM: UberHack :P + return; + } + + //if ( ps->torsoTimer > 0 ) { + if ( ps->stats[TORSOTIMER] > 0 ) { + return; // a high priority animation is running + } + + //ps->torsoAnim + ps->stats[TORSOANIM] = ( ( ps->stats[TORSOANIM] & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) + | anim; +} + +/** +* Start leg animation +*/ +static void PM_StartLegsAnim( int anim ) { + playerState_t *ps = pm->ps; + + /*if ( ps->stats[EMOTES] & EMOTE_CLAMP ) { + return; + }*/ + + if ( ps->pm_type >= PM_DEAD && + anim != BOTH_FALLDEATH1INAIR && + anim != BOTH_FALLDEATH1LAND ) + { + return; + } + + //if ( ps->introTime > 0 ) { //legsTimer + /*if ( ps->stats[LEGSTIMER] > 0 ) { //legsTimer + return; // a high priority animation is running + }*/ + + //ps->legsAnim + ps->stats[LEGSANIM] = ( ( ps->stats[LEGSANIM] & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) + | anim; +} + +/** +* Continues the legs animation. +*/ +static void PM_ContinueLegsAnim( int anim, qboolean overrideEmote ) { + playerState_t *ps = pm->ps; + + //override to return to idle after moving in an emote + if ( (ps->stats[EMOTES] & EMOTE_LOWER ) && ( !( ps->stats[EMOTES] & EMOTE_CLAMP_BODY) && !( ps->stats[EMOTES] & EMOTE_CLAMP_ALL) ) && !overrideEmote ) { + if ( ps->legsTimer>0 && ( ps->stats[LEGSANIM] & ~ANIM_TOGGLEBIT ) != bg_emoteList[ ps->legsTimer ].enumName && ( ps->stats[LEGSANIM] & ~ANIM_TOGGLEBIT ) != BOTH_GET_UP1 ) { + int anim2 = PM_GetAnim( ANIM_IDLE, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ); + + //Com_Printf( "KILL!\n"); + ps->stats[LEGSANIM] = ( ( ps->stats[LEGSANIM] & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim2; + } + } + + if ( (ps->stats[EMOTES] & EMOTE_CLAMP_BODY || ps->stats[EMOTES] & EMOTE_CLAMP_ALL ) && !overrideEmote ) { //EMOTE_LOWER + return; + } + + //if ( ( ps->legsAnim & ~ANIM_TOGGLEBIT ) == anim ) { + if ( ( ps->stats[LEGSANIM] & ~ANIM_TOGGLEBIT ) == anim ) { + return; + } + //if ( ps->introTime > 0 ) { //legsTimer + if ( ps->stats[LEGSTIMER] > 0 && !overrideEmote ) { //legsTimer + return; // a high priority animation is running + } + PM_StartLegsAnim( anim ); +} + +/** +* Continues the torso animation +*/ +static void PM_ContinueTorsoAnim( int anim, qboolean overrideEmote ) { + playerState_t *ps = pm->ps; + + if ( ps->stats[EMOTES] & EMOTE_UPPER && !overrideEmote ) { + return; + } + + //if ( ( ps->torsoAnim & ~ANIM_TOGGLEBIT ) == anim ) { + if ( ( ps->stats[TORSOANIM] & ~ANIM_TOGGLEBIT ) == anim ) { + return; + } + + //if ( ps->torsoTimer > 0 ) { + if ( ps->stats[TORSOTIMER] > 0 ) { + return; // a high priority animation is running + } + PM_StartTorsoAnim( anim, overrideEmote ); +} + +/** +* Force a legs animation +*/ +static void PM_ForceLegsAnim( int anim ) { + playerState_t *ps = pm->ps; + + //OMFG UBERHACK + //I'm lazy... client revive spawns players 1 unit over the ground. + //THat small fall enacts this, and subsequently screws up client revive animations + if ( ( ps->stats[LEGSANIM] & ~ANIM_TOGGLEBIT) != BOTH_GET_UP1 ) { + ps->stats[EMOTES] &= ~EMOTE_MASK_LOWER; + ps->stats[LEGSTIMER] = 0; //legsTimer + } + + PM_StartLegsAnim( anim ); +} + +/** +* Force a torso animation +*/ +static void PM_ForceTorsoAnim( int anim, qboolean overrideEmotes ) { + playerState_t *ps = pm->ps; + + if ( overrideEmotes && ( ps->stats[TORSOANIM] & ~ANIM_TOGGLEBIT) != BOTH_GET_UP1 ) { + ps->stats[EMOTES] &= ~EMOTE_MASK_UPPER; + ps->stats[TORSOTIMER] = 0; + } + //ps->stats[TORSOTIMER] = 0; + + PM_StartTorsoAnim( anim, overrideEmotes ); +} + +/* +================ +PM_Animate (RPG-X:J2J) + +TiM: Bookmark... this shows promise :) +LATER: Nope... scratch that. +Although it's good in the fact it assigns +the anim right, it doesn't take the timers +into account, meaning this would play for a +total of one clock cycle :P +================ +*/ +/*static void PM_DoEmote( void ) +{ + if ( pm->ps->stats[EMOTES] & EMOTE_LOWER ) { + pm->ps->viewheight = emoteList[pm->ps->legsAnim].viewHeight; + } +}*/ + +/*static void PM_DoEmote( void ) +{ + int EmoteType = 0; //0 = legs, 1 = torso, 2 = both + + //Bail out if no new emote or invalid. + if(CurrentEmote[pm->ps->clientNum] < 0 || CurrentEmote[pm->ps->clientNum] >= MAX_ANIMATIONS) + return; + + //Get animation type + if(CurrentEmote[pm->ps->clientNum] >= LEGS_WALKBACK1) + EmoteType = 0; + if(CurrentEmote[pm->ps->clientNum] >= TORSO_DROPWEAP1 && CurrentEmote[pm->ps->clientNum] <= TORSO_CARRY1) + EmoteType = 1; + if(CurrentEmote[pm->ps->clientNum] >= BOTH_DEATH1 && CurrentEmote[pm->ps->clientNum] <= BOTH_POWERUP1) + EmoteType = 2; + +//Check for higher priority animations + if(EmoteType == 0) + { + //ToDo: insert checking for high priority anims + } + else if(EmoteType == 1) + { + //ToDo: insert checking for high priority anims + } + else //Implies EmoteType == 2 + { + //ToDo: insert checking for high priority anims + } + + //Do the emote (play the anim) + if(EmoteType == 0 || EmoteType == 2) + { + PM_ForceLegsAnim(CurrentEmote[pm->ps->clientNum]); //CurrentEmote[pm->ps->clientNum] + //pm->ps->legsAnim = ( ( pm->ps->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) + //| CurrentEmote[pm->ps->clientNum]; + } + if(EmoteType == 1 || EmoteType == 2) + { + PM_ForceTorsoAnim(CurrentEmote[pm->ps->clientNum]); + //pm->ps->torsoAnim = ( ( pm->ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) + //| CurrentEmote[pm->ps->clientNum]; + } + + //Make -1 so it doesnt start the anim again. + CurrentEmote[pm->ps->clientNum] = -1; +}*/ + + +/* +================== +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; + playerState_t *ps = pm->ps; + + vel = 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->watertype & CONTENTS_LADDER) || pm->waterlevel <= 1 ) { + if ( (pm->watertype & CONTENTS_LADDER) || (pml.walking && !(pml.groundTrace.surfaceFlags & SURF_SLICK)) ) { + // if getting knocked back, no friction + if ( ! (ps->pm_flags & PMF_TIME_KNOCKBACK) ) { + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control*pm_friction*pml.frametime; + } + } + } + + // apply water friction even if just wading + if ( pm->waterlevel && !(pm->watertype & CONTENTS_LADDER) ) { + drop += speed*pm_waterfriction*pm->waterlevel*pml.frametime; + } + + //RPG-X | Phenix | 8/8/2004 + //Apply EVOSUIT friction (small) + if ( ps->powerups[PW_EVOSUIT] ) + { + drop += speed*pm_evosuitfriction*pml.frametime; + } + + // apply flying friction + if ( ps->powerups[PW_FLIGHT] || ps->pm_type == PM_SPECTATOR ) { + drop += speed*pm_flightfriction*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 +============ +*/ +/** +* \return 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 +================ +*/ +/** +* Determines the rotation of the legs reletive +* to the facing dir +*/ +static void PM_SetMovementDir( void ) { + playerState_t *ps = pm->ps; + usercmd_t *cmd = &pm->cmd; + + if ( cmd->forwardmove || cmd->rightmove ) { + if ( cmd->rightmove == 0 && cmd->forwardmove > 0 ) { + ps->movementDir = 0; + } else if ( cmd->rightmove < 0 && cmd->forwardmove > 0 ) { + ps->movementDir = 1; + } else if ( cmd->rightmove < 0 && cmd->forwardmove == 0 ) { + ps->movementDir = 2; + } else if ( cmd->rightmove < 0 && cmd->forwardmove < 0 ) { + ps->movementDir = 3; + } else if ( cmd->rightmove == 0 && cmd->forwardmove < 0 ) { + ps->movementDir = 4; + } else if ( cmd->rightmove > 0 && cmd->forwardmove < 0 ) { + ps->movementDir = 5; + } else if ( cmd->rightmove > 0 && cmd->forwardmove == 0 ) { + ps->movementDir = 6; + } else if ( cmd->rightmove > 0 && cmd->forwardmove > 0 ) { + 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 ( ps->movementDir == 2 ) { + ps->movementDir = 1; + } else if ( ps->movementDir == 6 ) { + ps->movementDir = 7; + } + } +} + + +/* +============= +PM_CheckJump +============= +*/ +/** +* Checks if jumping is allowed +*/ +static qboolean PM_CheckJump( void ) { + playerState_t *ps = pm->ps; + + if ( ps->pm_flags & PMF_RESPAWNED ) { + return qfalse; // don't allow jump until all buttons are up + } + + if ( pm->cmd.upmove < 10 ) { + // not holding jump + return qfalse; + } + + // must wait for jump to be released + if ( ps->pm_flags & PMF_JUMP_HELD ) { + // clear upmove so cmdscale doesn't lower running speed + pm->cmd.upmove = 0; + return qfalse; + } + + pml.groundPlane = qfalse; // jumping away + pml.walking = qfalse; + ps->pm_flags |= PMF_JUMP_HELD; + + ps->groundEntityNum = ENTITYNUM_NONE; + ps->velocity[2] = JUMP_VELOCITY; + + PM_AddEvent( EV_JUMP ); + + if ( pm->cmd.forwardmove >= 0 ) { + PM_ForceLegsAnim( PM_GetAnim( ANIM_JUMP, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ) ); //BOTH_JUMP + if ( ps->weaponstate == WEAPON_READY ) + PM_ForceTorsoAnim( PM_GetAnim( ANIM_JUMP, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qtrue ); + ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } else { + if ( ps->weaponstate == WEAPON_READY ) + PM_ForceTorsoAnim( PM_GetAnim( ANIM_JUMPB, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ), qtrue ); + PM_ForceLegsAnim( PM_GetAnim( ANIM_JUMPB, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ) ); //LEGS_JUMPB + ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + + return qtrue; +} + +/* +============= +PM_CheckWaterJump +============= +*/ +/** +* Checks if jumping out of water is allowed +*/ +static qboolean PM_CheckWaterJump( void ) { + vec3_t spot; + int cont; + vec3_t flatforward; + playerState_t *ps = pm->ps; + + if (ps->pm_time) { + return qfalse; + } + + // check for water jump + if ( pm->waterlevel != 2 ) { + return qfalse; + } + + if ( pm->watertype & CONTENTS_LADDER ) { + if (ps->velocity[2] <= 0) + return qfalse; + } + + flatforward[0] = pml.forward[0]; + flatforward[1] = pml.forward[1]; + flatforward[2] = 0; + VectorNormalize (flatforward); + + VectorMA (ps->origin, 30, flatforward, spot); + spot[2] += 4; + cont = pm->pointcontents (spot, ps->clientNum ); + if ( !(cont & CONTENTS_SOLID) ) { + return qfalse; + } + + spot[2] += 16; + cont = pm->pointcontents (spot, ps->clientNum ); + if ( cont ) { + return qfalse; + } + + // jump out of water + VectorScale (pml.forward, 200, ps->velocity); + ps->velocity[2] = 350; + + ps->pm_flags |= PMF_TIME_WATERJUMP; + ps->pm_time = 2000; + + return qtrue; +} + +//============================================================================ + + +/* +=================== +PM_WaterJumpMove +=================== +*/ +/** +* Flying out of the water +*/ +static void PM_WaterJumpMove( void ) { + playerState_t *ps = pm->ps; + // waterjump has no control, but falls + + PM_StepSlideMove( qtrue ); + + ps->velocity[2] -= ps->gravity * pml.frametime; + if (ps->velocity[2] < 0) { + // cancel as soon as we are falling down again + ps->pm_flags &= ~PMF_ALL_TIMES; + ps->pm_time = 0; + } +} + +/* +=================== +PM_WaterMove + +=================== +*/ +/** +* Handles movement in water +*/ +static void PM_WaterMove( void ) { + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + float vel; + playerState_t *ps = pm->ps; + + if ( PM_CheckWaterJump() ) { + PM_WaterJumpMove(); + return; + } +#if 0 + // jump = head for surface + if ( pm->cmd.upmove >= 10 ) { + if (ps->velocity[2] > -300) { + if ( pm->watertype == CONTENTS_WATER ) { + ps->velocity[2] = 100; + } else if (pm->watertype == CONTENTS_SLIME) { + ps->velocity[2] = 80; + } else { + ps->velocity[2] = 50; + } + } + } +#endif + PM_Friction (); + + scale = PM_CmdScale( &pm->cmd ); + // + // user intentions + // + if ( !scale ) { + wishvel[0] = 0; + wishvel[1] = 0; + if ( pm->watertype & CONTENTS_LADDER ) { + wishvel[2] = 0; + } else { + 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 ( pm->watertype & CONTENTS_LADDER ) //ladder + { + if ( wishspeed > ps->speed * pm_ladderScale ) { + wishspeed = ps->speed * pm_ladderScale; + } + PM_Accelerate( wishdir, wishspeed, pm_flyaccelerate ); + } else { + if ( wishspeed > ps->speed * pm_swimScale ) { + wishspeed = ps->speed * pm_swimScale; + } + PM_Accelerate( wishdir, wishspeed, pm_wateraccelerate ); + } + + // make sure we can go up slopes easily under water + if ( pml.groundPlane && DotProduct( ps->velocity, pml.groundTrace.plane.normal ) < 0 ) { + vel = VectorLength(ps->velocity); + // slide along the ground plane + PM_ClipVelocity (ps->velocity, pml.groundTrace.plane.normal, + ps->velocity, OVERCLIP ); + + VectorNormalize(ps->velocity); + VectorScale(ps->velocity, vel, ps->velocity); + } + + PM_SlideMove( qfalse ); +} + + + +/* +=================== +PM_FlyMove + +Only with the flight powerup +TiM: Good... if this handles +spectators too, I'm sunk >.< +Oh crap... it does lol +=================== +*/ +/** +* Handles fly movement (e.g. flight powerup or spectator movement) +*/ +static void PM_FlyMove( void ) { + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + playerState_t *ps = pm->ps; + + // 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++) { + if ( ps->pm_type == PM_SPECTATOR ) { + wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; + } + else { + //TiM - Vertical is no longer a hardcoded constant direction + wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + + scale * pml.right[i]*pm->cmd.rightmove + + scale * pml.up[i]* pm->cmd.upmove; + + //TiM - Set directions + PM_SetMovementDir(); + } + } + + if ( ps->pm_type == PM_SPECTATOR ) + wishvel[2] += scale * pm->cmd.upmove; + } + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + PM_Accelerate (wishdir, wishspeed, pm_flyaccelerate); + + if ( ps->weaponstate == WEAPON_READY ) + PM_ContinueTorsoAnim( PM_GetAnim( ANIM_FLY, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse ); + PM_ContinueLegsAnim( PM_GetAnim( ANIM_FLY, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ), qtrue ); + + PM_StepSlideMove( qfalse ); +} + + +/* +=================== +PM_AirMove + +=================== +*/ +/** +* Handles movement during air time (e.g. falling) +*/ +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 +=================== +*/ +/** +* Handles walk movement +*/ +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; + playerState_t *ps = pm->ps; + + if ( pm->waterlevel > 2 && DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 ) { + // begin swimming + PM_WaterMove(); + return; + } + + + if ( PM_CheckJump () ) { + // jumped away + if ( pm->waterlevel > 1 ) { + PM_WaterMove(); + } else { + PM_AirMove(); + } + return; + } + + PM_Friction (); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + //scale = 0.4; //TiM + + // 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 ( ps->pm_flags & PMF_DUCKED ) { + if ( wishspeed > ps->speed * pm_duckScale ) { + wishspeed = ps->speed * pm_duckScale; + } + } + + // clamp the speed lower if wading or walking on the bottom + if ( pm->waterlevel ) { + float waterScale; + + waterScale = pm->waterlevel / 3.0; + waterScale = 1.0 - ( 1.0 - pm_swimScale ) * waterScale; + if ( wishspeed > ps->speed * waterScale ) { + wishspeed = 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 ) || ps->pm_flags & PMF_TIME_KNOCKBACK ) { + accelerate = pm_airaccelerate; + } else { + accelerate = pm_accelerate; + } + + PM_Accelerate (wishdir, wishspeed, accelerate); + + //Com_Printf("velocity = %1.1f %1.1f %1.1f\n", ps->velocity[0], ps->velocity[1], ps->velocity[2]); + //Com_Printf("velocity1 = %1.1f\n", VectorLength(ps->velocity)); + + if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || ps->pm_flags & PMF_TIME_KNOCKBACK ) { + ps->velocity[2] -= ps->gravity * pml.frametime; + } else { + // don't reset the z velocity for slopes +// ps->velocity[2] = 0; + } + + vel = VectorLength(ps->velocity); + + // slide along the ground plane + PM_ClipVelocity (ps->velocity, pml.groundTrace.plane.normal, + ps->velocity, OVERCLIP ); + + // don't decrease velocity when going up or down a slope + VectorNormalize(ps->velocity); + VectorScale(ps->velocity, vel, ps->velocity); + + // don't do anything if standing still + if (!ps->velocity[0] && !ps->velocity[1]) { + return; + } + + PM_StepSlideMove( qfalse ); + + //Com_Printf("velocity2 = %1.1f\n", VectorLength(ps->velocity)); + +} + + +/* +============== +PM_DeadMove +============== +*/ +/** +* Handles movement while dead +*/ +static void PM_DeadMove( void ) { + float forward; + playerState_t *ps = pm->ps; + + if ( !pml.walking ) { + return; + } + + // extra friction + + forward = VectorLength (ps->velocity); + forward -= 20; + if ( forward <= 0 ) { + VectorClear (ps->velocity); + } else { + VectorNormalize (ps->velocity); + VectorScale (ps->velocity, forward, ps->velocity); + } +} + + +/* +=============== +PM_NoclipMove +=============== +*/ +/** +* Handles noclip movement +*/ +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; + playerState_t *ps = pm->ps; + + ps->viewheight = DEFAULT_VIEWHEIGHT; + + // friction + + speed = VectorLength (ps->velocity); + if (speed < 1) + { + VectorCopy (vec3_origin, 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 (ps->velocity, newspeed, ps->velocity); + } + + // accelerate + scale = PM_CmdScale( &pm->cmd ); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + for (i=0 ; i<3 ; i++) + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + wishvel[2] += pm->cmd.upmove; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + + PM_Accelerate( wishdir, wishspeed, pm_accelerate ); + + // move + VectorMA (ps->origin, pml.frametime, ps->velocity, ps->origin); +} + + +/* +=================== +PM_FreezeMove +=================== +*/ +/** +* Handles movement whole freezed +*/ +static void PM_FreezeMove( void ) +{ + trace_t trace; + short temp, i; + vec3_t moveto; + playerState_t *ps = pm->ps; + + pm->mins[0] = DEFAULT_MINS_0; //-15 + pm->mins[1] = DEFAULT_MINS_1; + + pm->maxs[0] = DEFAULT_MAXS_0; //15 + pm->maxs[1] = DEFAULT_MAXS_1; + + pm->mins[2] = MINS_Z; + // stand up if possible + if (ps->pm_flags & PMF_DUCKED) + { + // try to stand up + pm->maxs[2] = 36; //32 + pm->trace (&trace, ps->origin, pm->mins, pm->maxs, ps->origin, ps->clientNum, pm->tracemask ); + if (!trace.allsolid) + ps->pm_flags &= ~PMF_DUCKED; + } + + if (ps->pm_flags & PMF_DUCKED && ( !(ps->stats[EMOTES] & EMOTE_LOWER) && !ps->powerups[PW_FLIGHT] && !( ( ps->powerups[PW_EVOSUIT] ) && ( ps->gravity == 0 ) ) ) ) + { + pm->maxs[2] = 16; + ps->viewheight = CROUCH_VIEWHEIGHT; + } + else + { + if ( ps->stats[EMOTES] & EMOTE_LOWER && ps->legsTimer > 0 ) { + pm->maxs[2] = bg_emoteList[ps->legsTimer].hitBoxHeight; + ps->viewheight = bg_emoteList[ps->legsTimer].viewHeight; + //Com_Printf( S_COLOR_RED "%f", bg_emoteList[ps->legsTimer].viewHeight ); + } + else { + //TiM - essentially flip the bounding box + /*if ( ps->powerups[PW_FLIGHT] && Q_fabs( ps->viewangles[PITCH] ) > 89.0f ) { + pm->maxs[2] = 92; + pm->mins[2] = 32; + ps->viewheight = DEFAULT_VIEWHEIGHT; + }*/ + //else { + //TiM - Copied above + /*pm->maxs[2] = 36; + pm->mins[2] = MINS_Z; + ps->viewheight = DEFAULT_VIEWHEIGHT;*/ + //} + ps->viewheight = DEFAULT_VIEWHEIGHT; + } + } + + // circularly clamp the angles with deltas + for (i=0 ; i<3 ; i++) { + temp = pm->cmd.angles[i] + ps->delta_angles[i]; + if ( i == PITCH ) { + // don't let the player look up or down more than 90 degrees + if ( temp > 16000 ) { + ps->delta_angles[i] = 16000 - pm->cmd.angles[i]; + temp = 16000; + } else if ( temp < -16000 ) { + ps->delta_angles[i] = -16000 - pm->cmd.angles[i]; + temp = -16000; + } + } +// ps->viewangles[i] = SHORT2ANGLE(temp); // Clear the view angles, but don't set them. + } + + VectorCopy (ps->origin, moveto); + moveto[2] -= 16; + + // test the player position if they were a stepheight higher + pm->trace (&trace, ps->origin, pm->mins, pm->maxs, moveto, ps->clientNum, pm->tracemask); + if ( trace.fraction < 1.0) + { // Something just below, snap to it, to prevent a little "hop" after the holodeck fades in. + VectorCopy (trace.endpos, ps->origin); + ps->groundEntityNum = trace.entityNum; + + // Touch it. + PM_AddTouchEnt( trace.entityNum ); + + } +} + + +//============================================================================ + +//RPG-X | GSIO01 | 20/05/2009: +/** +* Get the corresponding landing sound for each surface type +*/ +static int PM_LandsoundForSurface( int fallType ) { + if( pm->ps->stats[PW_INVIS] ) + return 0; + + switch(fallType) { + case 1: + if(pml.groundTrace.surfaceFlags & SURF_GRASS) + return EV_FALL_MEDIUM_GRASS; + else if(pml.groundTrace.surfaceFlags & SURF_GRAVEL) + return EV_FALL_MEDIUM_GRAVEL; + else if(pml.groundTrace.surfaceFlags & SURF_SNOW) + return EV_FALL_MEDIUM_SNOW; + else if(pml.groundTrace.surfaceFlags & SURF_WOOD) + return EV_FALL_MEDIUM_WOOD; + else + return EV_FALL_MEDIUM; + break; + case 2: + if(pml.groundTrace.surfaceFlags & SURF_GRASS) + return EV_FALL_FAR_GRASS; + else if(pml.groundTrace.surfaceFlags & SURF_GRAVEL) + return EV_FALL_FAR_GRAVEL; + else if(pml.groundTrace.surfaceFlags & SURF_SNOW) + return EV_FALL_FAR_SNOW; + else if(pml.groundTrace.surfaceFlags & SURF_WOOD) + return EV_FALL_FAR_WOOD; + else + return EV_FALL_FAR; + break; + default: + if(pml.groundTrace.surfaceFlags & SURF_GRASS) + return EV_FALL_SHORT_GRASS; + else if(pml.groundTrace.surfaceFlags & SURF_GRAVEL) + return EV_FALL_SHORT_GRAVEL; + else if(pml.groundTrace.surfaceFlags & SURF_SNOW) + return EV_FALL_SHORT_SNOW; + else if(pml.groundTrace.surfaceFlags & SURF_WOOD) + return EV_FALL_SHORT_WOOD; + else + return EV_FALL_SHORT; + break; + } +} + +/* +================ +PM_FootstepForSurface +================ +*/ +/** +* \return an event number apropriate for the groundsurface +*/ +static int PM_FootstepForSurface( void ) { + //cloaked people make no noise + if ( pml.groundTrace.surfaceFlags & SURF_NOSTEPS || pm->ps->stats[PW_INVIS] ) { + return 0; + } + if ( pml.groundTrace.surfaceFlags & SURF_METALSTEPS ) { + return EV_FOOTSTEP_METAL; + } + //RPG-X | GSIO01 | 20.05.2009 | START MOD + if ( pml.groundTrace.surfaceFlags & SURF_GRASS ) { + return EV_FOOTSTEP_GRASS; + } + if ( pml.groundTrace.surfaceFlags & SURF_GRAVEL ) { + return EV_FOOTSTEP_GRAVEL; + } + if ( pml.groundTrace.surfaceFlags & SURF_SNOW ) { + return EV_FOOTSTEP_SNOW; + } + if ( pml.groundTrace.surfaceFlags & SURF_WOOD ) { + return EV_FOOTSTEP_WOOD; + } + //RPG-X | GSIO01 | 20.05.2009 | END MOD + return EV_FOOTSTEP; +} + + +/* +================= +PM_CrashLand +================= +*/ +/** +* Check for hard landings that generate sound events +*/ +static void PM_CrashLand( void ) { + float delta; + float dist; + float vel, acc; + float t; + float a, b, c, den; + playerState_t *ps = pm->ps; + + // decide which landing animation to use + if ( ps->pm_flags & PMF_BACKWARDS_JUMP ) { + // PM_ForceLegsAnim( LEGS_LANDB ); + } else { + // PM_ForceLegsAnim( LEGS_LAND ); + } + +// ps->legsTimer = 0; //TIMER_LAND + + // calculate the exact velocity on landing + dist = ps->origin[2] - pml.previous_origin[2]; + vel = pml.previous_velocity[2]; + acc = -ps->gravity; + + a = acc / 2; + b = vel; + c = -dist; + + den = b * b - 4 * a * c; + if ( den < 0 ) { + return; + } + t = (-b - sqrt( den ) ) / ( 2 * a ); + + delta = vel + t * acc; + delta = delta*delta * 0.0001; + + // ducking while falling doubles damage + if ( ps->pm_flags & PMF_DUCKED ) { + delta *= 2; + } + + // never take falling damage if completely underwater + if ( pm->waterlevel == 3 ) { + return; + } + + // reduce falling damage if there is standing water + if ( pm->waterlevel == 2 ) { + delta *= 0.25; + } + if ( pm->waterlevel == 1 ) { + delta *= 0.5; + } + + if ( delta < 1 ) { + return; + } + + // create a local entity event to play the sound + + // SURF_NODAMAGE is used for bounce pads where you don't ever + // want to take damage or play a crunch sound + if ( !(pml.groundTrace.surfaceFlags & SURF_NODAMAGE) ) { + if ( delta > 55 || ps->stats[STAT_HEALTH] <= 1 ) { //60 //TiM a bit hacky, but I want this to play any time we fall when dead + PM_AddEvent( /*EV_FALL_FAR*/PM_LandsoundForSurface(2) ); //GSIO01 | 20/05/2009 + } else if ( delta > 35 ) { //40 + // this is a pain grunt, so don't play it if dead + if ( ps->stats[STAT_HEALTH] > 1 ) { //0 + PM_AddEvent( /*EV_FALL_MEDIUM*/PM_LandsoundForSurface(1) ); //GSIO01 | 20/05/2009 + } + } else if ( delta > 5 ) { //7 + PM_AddEvent( /*EV_FALL_SHORT*/ PM_LandsoundForSurface(0) ); //GSIO01 | 20/05/2009 + } else { + PM_AddEvent( PM_FootstepForSurface() ); + } + } + + // start footstep cycle over + ps->bobCycle = 0; //TiM: was commented out... :P +} + + + +/* +============= +PM_CorrectAllSolid +============= +*/ +static void PM_CorrectAllSolid( void ) { + if ( pm->debugLevel ) { + Com_Printf("%i:allsolid\n", c_pmove); + } + + // FIXME: jitter around + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = 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; + playerState_t *ps = pm->ps; + + if ( 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( ps->origin, point ); + point[2] -= 64; + + pm->trace (&trace, ps->origin, pm->mins, pm->maxs, point, ps->clientNum, pm->tracemask); + if ( trace.fraction == 1.0 && ( ps->stats[LEGSANIM] & ~ANIM_TOGGLEBIT) != BOTH_GET_UP1 ) { + if ( pm->cmd.forwardmove >= 0 ) { + PM_ForceLegsAnim( PM_GetAnim( ANIM_JUMP, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ) ); + if ( ps->weaponstate == WEAPON_READY ) + PM_ForceTorsoAnim( PM_GetAnim( ANIM_JUMP, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qtrue ); + ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } else { + PM_ForceLegsAnim( PM_GetAnim( ANIM_JUMPB, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ) ); + if ( ps->weaponstate == WEAPON_READY ) + PM_ForceTorsoAnim( PM_GetAnim( ANIM_JUMPB, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qtrue ); + ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + } + } + + ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; +} + + +/* +============= +PM_GroundTrace +============= +*/ +/** +* Does ad trace to the ground +*/ +static void PM_GroundTrace( void ) { + vec3_t point; + trace_t trace; + playerState_t *ps = pm->ps; + + point[0] = ps->origin[0]; + point[1] = ps->origin[1]; + point[2] = ps->origin[2] - 0.25; + + pm->trace (&trace, ps->origin, pm->mins, pm->maxs, point, ps->clientNum, pm->tracemask); + pml.groundTrace = trace; + + // do something corrective if the trace starts in a solid... + if ( trace.allsolid ) { + PM_CorrectAllSolid(); + return; + } + + // if the trace didn't hit anything, we are in free fall + if ( trace.fraction == 1.0 ) { + PM_GroundTraceMissed(); + pml.groundPlane = qfalse; + pml.walking = qfalse; + return; + } + + // check if getting thrown off the ground + if ( ps->velocity[2] > 0 && DotProduct( ps->velocity, trace.plane.normal ) > 10 && ( ps->stats[LEGSANIM] & ~ANIM_TOGGLEBIT) != BOTH_GET_UP1 ) { + if ( pm->debugLevel ) { + Com_Printf("%i:kickoff\n", c_pmove); + } + // go into jump animation + if ( pm->cmd.forwardmove >= 0 ) { + PM_ForceLegsAnim( PM_GetAnim( ANIM_JUMP, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ) ); + if ( ps->weaponstate == WEAPON_READY ) + PM_ForceTorsoAnim( PM_GetAnim( ANIM_JUMP, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qtrue ); + ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } else { + PM_ForceLegsAnim( PM_GetAnim( ANIM_JUMPB, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ) ); + if ( ps->weaponstate == WEAPON_READY ) + PM_ForceTorsoAnim( PM_GetAnim( ANIM_JUMPB, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qtrue ); + ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + return; + } + + // slopes that are too steep will not be considered onground + if ( trace.plane.normal[2] < MIN_WALK_NORMAL ) { + if ( pm->debugLevel ) { + Com_Printf("%i:steep\n", c_pmove); + } + // FIXME: if they can't slide down the slope, let them + // walk (sharp crevices) + 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 (ps->pm_flags & PMF_TIME_WATERJUMP) + { + ps->pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND); + ps->pm_time = 0; + } + + if ( ps->groundEntityNum == ENTITYNUM_NONE ) { + // just hit the ground + if ( pm->debugLevel ) { + Com_Printf("%i:Land\n", c_pmove); + } + + PM_CrashLand(); + + // don't do landing time if we were just going down a slope + if ( pml.previous_velocity[2] < -200 ) { + // don't allow another jump for a little while + ps->pm_flags |= PMF_TIME_LAND; + ps->pm_time = 250; + } + } + + 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 +============= +*/ +/** +* Set water level +*/ +static void PM_SetWaterLevel( void ) { + vec3_t point; + int cont; + int sample1; + int sample2; + playerState_t *ps = pm->ps; + + // + // get waterlevel, accounting for ducking + // + pm->waterlevel = 0; + pm->watertype = 0; + + point[0] = ps->origin[0]; + point[1] = ps->origin[1]; + point[2] = ps->origin[2] + MINS_Z + 1; + cont = pm->pointcontents( point, ps->clientNum ); + + if ( cont & (MASK_WATER|CONTENTS_LADDER) ) { + sample2 = ps->viewheight - MINS_Z; + sample1 = sample2 / 2; + + pm->watertype = cont; + pm->waterlevel = 1; + point[2] = ps->origin[2] + MINS_Z + sample1; + cont = pm->pointcontents (point, ps->clientNum ); + if ( cont & (MASK_WATER|CONTENTS_LADDER) ) { + pm->waterlevel = 2; + point[2] = ps->origin[2] + MINS_Z + sample2; + cont = pm->pointcontents (point, ps->clientNum ); + if ( cont & (MASK_WATER|CONTENTS_LADDER) ){ + pm->waterlevel = 3; + } + } + } + +} + + + +/* +============== +PM_CheckDuck +============== +*/ +/** +* Sets mins, maxs, and pm->ps->viewheight +*/ +static void PM_CheckDuck (void) +{ + trace_t trace; + playerState_t *ps = pm->ps; + + pm->mins[0] = DEFAULT_MINS_0; //-15 + pm->mins[1] = DEFAULT_MINS_1; + + pm->maxs[0] = DEFAULT_MAXS_0; + pm->maxs[1] = DEFAULT_MAXS_1; + + pm->mins[2] = MINS_Z; + + if (ps->pm_type == PM_DEAD) + { + pm->maxs[2] = -8; + ps->viewheight = DEAD_VIEWHEIGHT; + return; + } + + if (pm->cmd.upmove < 0) + { // duck + ps->pm_flags |= PMF_DUCKED; + } + else + { // stand up if possible + if (ps->pm_flags & PMF_DUCKED) + { + // try to stand up + pm->maxs[2] = 36; //32 + pm->trace (&trace, ps->origin, pm->mins, pm->maxs, ps->origin, ps->clientNum, pm->tracemask ); + if (!trace.allsolid) + ps->pm_flags &= ~PMF_DUCKED; + } + } + + if ( ps->pm_flags & PMF_DUCKED && !( (ps->stats[EMOTES] & EMOTE_LOWER) || ps->powerups[PW_FLIGHT] || ( ps->powerups[PW_EVOSUIT] && ps->gravity == 0 ) ) ) + { + pm->maxs[2] = 16; + ps->viewheight = CROUCH_VIEWHEIGHT; + } + else + { + if ( ps->stats[EMOTES] & EMOTE_LOWER && ps->legsTimer > 0 ) { + pm->maxs[2] = bg_emoteList[ps->legsTimer].hitBoxHeight; + ps->viewheight = bg_emoteList[ps->legsTimer].viewHeight; + + //Com_Printf( S_COLOR_RED "legsTimer = %i, viewHeight = %i\n", ps->legsTimer, ps->viewheight ); + } + else { + //TiM - essentially flip the bounding box + /*if ( ps->powerups[PW_FLIGHT] && Q_fabs( ps->viewangles[PITCH] ) > 89.0f ) { + pm->maxs[2] = 92; + pm->mins[2] = 32; + ps->viewheight = DEFAULT_VIEWHEIGHT; + }*/ + //else { + pm->maxs[2] = 36; + pm->mins[2] = MINS_Z; + ps->viewheight = DEFAULT_VIEWHEIGHT; + //} + } + } + + //Com_Printf( "Viewheight is %i\n", ps->viewheight ); +} + + + +//=================================================================== + +//static qboolean ps->didFly; +//static + +/* +=============== +PM_Footsteps +=============== +*/ +/** +* Does what it name suggests it handles footsteps. +*/ +static void PM_Footsteps( void ) { + float bobmove; + int old; + qboolean footstep; + playerState_t *ps = pm->ps; + //qboolean didFly; + + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // + pm->xyspeed = sqrt( ps->velocity[0] * ps->velocity[0] + + ps->velocity[1] * ps->velocity[1] ); + + pm->xyzspeed = sqrt( ps->velocity[0] * ps->velocity[0] //XVel - left + right + + ps->velocity[1] * ps->velocity[1] //YVel - forward + back + + ps->velocity[2] * ps->velocity[2] ); //ZVel - up + down + + //RPG-X : TiM ***************************************************** + //Cheesy Halo style death flying! + if ( ps->stats[STAT_HEALTH] <= 1 && !ps->powerups[PW_QUAD] && !ps->powerups[PW_BEAM_OUT] ) { //if dead + /*TiM: clip brushes register as ENTITYNUM_NONE + (So if they landed on a shuttle model for instance, the fall anim would still loop O_o ) + so they gotta be moving as well to trigger this*/ + + //Com_Printf("groundEnt = %i, speed = %f, animState = %i\n",ps->groundEntityNum, pm->xyzspeed, ps->pm_flags); + + if ( ps->groundEntityNum == ENTITYNUM_NONE && pm->xyzspeed && pm->waterlevel < 2 ) { + + ps->pm_flags |= ANIM_DIDFLY; + + PM_ContinueLegsAnim( BOTH_FALLDEATH1INAIR, qtrue ); + + if ( ps->weaponstate == WEAPON_READY ) + { + PM_ContinueTorsoAnim( BOTH_FALLDEATH1INAIR, qtrue ); + } + } + + else { + if ( ps->pm_flags & ANIM_DIDFLY ) { + //TiM: Save flags. Use anim nums if possible + //if (ps->torsoAnim == BOTH_FALLDEATH1INAIR && ps->legsAnim == BOTH_FALLDEATH1INAIR ) { + PM_ContinueLegsAnim( BOTH_FALLDEATH1LAND, qtrue ); + + + if ( ps->weaponstate == WEAPON_READY ) + { + PM_ContinueTorsoAnim( BOTH_FALLDEATH1LAND, qtrue ); + } + + } + } + return; + } + else { //Reset splat boolean + if ( ps->pm_flags & ANIM_DIDFLY && ps->pm_type != PM_DEAD ) { + ps->pm_flags &= ~ANIM_DIDFLY; + } + } + + //RPG-X : TiM ***************************************************** + //Ladder Animations + + //If not on ladder, reset + if ( !(pm->watertype & CONTENTS_LADDER) ) + { + if ( ps->pm_flags & ANIM_ONLADDER ) { + ps->pm_flags &= ~( ANIM_ONLADDER ); + } + + if ( ( !(ps->stats[EMOTES] & EMOTE_UPPER ) || !(ps->stats[EMOTES] & EMOTE_LOWER ) ) + && ps->stats[EMOTES] & EMOTE_CLAMP_BODY ) { + ps->stats[EMOTES] &= ~EMOTE_CLAMP_BODY; + } + //ps->pm_flags &= ~( ANIM_OFFLADDER ); + } + + //If on ladder, but not touching the ground + if ( (pm->watertype & CONTENTS_LADDER) && ( ps->groundEntityNum == ENTITYNUM_NONE ) ) + { + if ( !(ps->pm_flags & ANIM_ONLADDER) ) { + ps->pm_flags |= ANIM_ONLADDER ; + ps->stats[EMOTES] |= EMOTE_CLAMP_BODY; + } + //ps->pm_flags &= ~( ANIM_OFFLADDER ); + } + + //If was going down the ladder, and hit the floor +/* if ( !(ps->pm_flags & (ANIM_ONLADDER) ) && + !(ps->pm_flags & (ANIM_OFFLADDER) ) && + ( pm->watertype & CONTENTS_LADDER) && + ( ps->groundEntityNum != ENTITYNUM_NONE ) ) + { + ps->pm_flags |= (ANIM_ONLADDER); + ps->pm_flags &= ~(ANIM_OFFLADDER); + + if ( ps->legsAnim == BOTH_LADDER_DWN1 || ps->legsAnim == BOTH_LADDER_IDLE) { //Going DOWN! + ps->pm_flags &= ~(ANIM_ONLADDER); + ps->pm_flags |= (ANIM_OFFLADDER); + } + } +*/ + //Com_Printf("pm->onLadder = %i, pm->offLadder = %i, vel = %i\n", pm->onLadder, pm->offLadder, ps->velocity[2] ); + + //Transition anim to get off ladder + if ( ( pm->watertype & CONTENTS_LADDER) && ( ps->groundEntityNum != ENTITYNUM_NONE ) && (ps->pm_flags & ANIM_ONLADDER) ) {//We JUST hit a ladder on the ground + PM_ContinueLegsAnim( BOTH_OFFLADDER_BOT1, qtrue ); + //ps->legsTimer = TIMER_GESTURE; + + if ( ps->weaponstate == WEAPON_READY ) + { + PM_ContinueTorsoAnim( BOTH_OFFLADDER_BOT1, qtrue ); + //ps->torsoTimer = TIMER_GESTURE; + } + //pm->offLadder = qtrue; + //if ( !(ps->pm_flags & ANIM_UPPER_LOOPING) ) { + ps->stats[EMOTES] &= ~EMOTE_CLAMP_BODY; + //} + + return; + } + + //Transition anim to get on ladder + if ( ( pm->watertype & CONTENTS_LADDER) && ( ps->groundEntityNum != ENTITYNUM_NONE ) && !(ps->pm_flags & ANIM_ONLADDER) ) {//We JUST hit a ladder on the ground + PM_ContinueLegsAnim( BOTH_ONLADDER_BOT1, qtrue ); + //ps->legsTimer = TIMER_GESTURE; + + if ( ps->weaponstate == WEAPON_READY ) + { + PM_ContinueTorsoAnim( BOTH_ONLADDER_BOT1, qtrue ); + //ps->torsoTimer = TIMER_GESTURE; + } + //pm->onLadder = qfalse; + + return; + } + + if ( ps->groundEntityNum == ENTITYNUM_NONE ) { + if (pm->watertype & CONTENTS_LADDER) + {//FIXME: check for watertype, save waterlevel for whether to play + //the get off ladder transition anim + + if ( ps->velocity[2] ) + {//going up or down it + int anim; + if ( ps->velocity[2] > 0 ) + { + anim = BOTH_LADDER_UP1; + } + else + { + anim = BOTH_LADDER_DWN1; + } + PM_ContinueLegsAnim( anim, qtrue ); + //if ( pm->waterlevel >= 2 ) //arms on ladder + if ( ps->weaponstate == WEAPON_READY ) + { + PM_ContinueTorsoAnim( anim, qtrue ); + } + if (fabs(ps->velocity[2]) >5) { + bobmove = 0.005 * fabs(ps->velocity[2]); // climbing bobs slow + if (bobmove > 0.3) + bobmove = 0.3F; + } + } + else + { + PM_ContinueLegsAnim( BOTH_LADDER_IDLE, qtrue ); + //ps->legsTimer += 300; + //if ( pm->waterlevel >= 2 ) //arms on ladder + if ( ps->weaponstate == WEAPON_READY ) + { + PM_ContinueTorsoAnim( BOTH_LADDER_IDLE, qtrue ); + //ps->torsoTimer += 300; + } + } + return; + }//****************************************************************** + else { + // airborne leaves position in cycle intact, but doesn't advance + if ( pm->waterlevel > 2 ) { //TiM: swimming is more hardcore now //1 + if ( ps->weaponstate == WEAPON_READY ) + PM_ContinueTorsoAnim( PM_GetAnim( ANIM_SWIM, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qtrue ); + PM_ContinueLegsAnim( PM_GetAnim( ANIM_SWIM, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ), qtrue ); + } /*else if ( ps->pm_flags & PMF_DUCKED ) { + PM_ContinueLegsAnim( BOTH_CROUCH2IDLE ); //BOTH_CROUCH1IDLE + }*/ + + return; + } + } + + + //Com_Printf( "Speed: %f\n", pm->xyspeed ); + + // if not trying to move + if ( ( !ps->speed || pm->xyspeed < 1.0f || !(pm->cmd.forwardmove || pm->cmd.rightmove) ) + && pm->waterlevel < 3 + && !ps->powerups[PW_FLIGHT] && !(( ps->powerups[PW_EVOSUIT] ) && ( ps->gravity == 0 )) + /*&& !( pm->watertype & MASK_WATER )*/ ) + { + //Com_Printf("Truuue\n" ); + + if ( pm->xyspeed > 1.0f && !( ps->pm_flags & PMF_DUCKED ) && !( ps->stats[EMOTES] & EMOTE_LOWER ) ) + { //TiM: When you want to duck, you will duck. no delays + if ( !( pm->cmd.buttons & BUTTON_WALKING ) && !(ps->pm_flags & PMF_DUCKED) ) { + if ( ps->weaponstate == WEAPON_READY && !PM_HoldingSpillableWeapon() ) { + PM_ContinueTorsoAnim( BOTH_RUN1STOP, qtrue ); //BOTH_RUN1STOP + } + PM_ContinueLegsAnim( BOTH_RUN1STOP, qtrue ); + } + + return; + } + else { // <5 + ps->bobCycle = 0; // start at beginning of cycle again + if ( ps->pm_flags & PMF_DUCKED && !(ps->stats[EMOTES] & EMOTE_LOWER ) ) { + + if ( ps->weaponstate == WEAPON_READY ) { + PM_ContinueTorsoAnim( PM_GetAnim( ANIM_CROUCH, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse ); + } + PM_ContinueLegsAnim( PM_GetAnim( ANIM_CROUCH, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ), qtrue ); + + /*if ( !(ps->pm_flags & ANIM_CROUCHING) ) { //okay, we've obviously JUST crouched... + ps->pm_flags |= ANIM_CROUCHING; + /*if ( ps->weaponstate == WEAPON_READY ) { + PM_StartTorsoAnim( PM_GetAnim( "docrouch", qtrue ) ); + ps->torsoTimer += 1200; + } + + PM_StartLegsAnim( PM_GetAnim( "docrouch", qfalse ) ); + ps->legsTimer += 1200;*/ + //} + + } else { + + if ( ps->weaponstate == WEAPON_READY ) + PM_ContinueTorsoAnim( PM_GetAnim( ANIM_IDLE, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse ); + PM_ContinueLegsAnim( PM_GetAnim( ANIM_IDLE, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ), qfalse ); + + /*if ( (ps->pm_flags & ANIM_CROUCHING) ) { //okay, we've obviously JUST uncrouched... + ps->pm_flags &= ~ANIM_CROUCHING; + + if ( ps->weaponstate == WEAPON_READY ) { + PM_StartTorsoAnim( PM_GetAnim( ANIM_UNCROUCH, qtrue ) ); + ps->torsoTimer = 1100; + //Com_Printf("Anim: %i, timer: %i\n", ps->torsoAnim, ps->torsoTimer); + } + + PM_StartLegsAnim( PM_GetAnim( ANIM_UNCROUCH, qfalse ) ); + ps->introTime = 1100; //legsTimer + }*/ + + } + } + return; + } + + footstep = qfalse; + + //TiM : in case we're part-way thru a transition anim, + //reset the anim timer + //ps->torsoTimer = 0; + //ps->legsTimer = 0; + + //TiM : Kill this when swimming as it screws up animations + //Also... kill when speed is 0.. running on the spot is silly lol + //Also, disable when flying. It looks ludricrous if we run upside down lol + if ( pm->waterlevel == 3 || ps->speed == 0 || ps->powerups[PW_FLIGHT] > 0 || pm->xyspeed < 1.0f || (( ps->powerups[PW_EVOSUIT] ) && ( ps->gravity == 0 )) ) + { + return; + } + + if ( ps->pm_flags & PMF_DUCKED ) { + bobmove = 0.5; // ducked characters bob much faster + //HACK coz this damn thing screws up crouch firing anims otherwise T_T + if ( ps->weaponstate == WEAPON_READY ) { + PM_ContinueTorsoAnim( PM_GetAnim( ANIM_CROUCHWALK, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse ); + } + + PM_ContinueLegsAnim( PM_GetAnim( ANIM_CROUCHWALK, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ), qtrue ); + // ducked characters never play footsteps + } + else if ( ps->pm_flags & PMF_BACKWARDS_RUN ) + { + if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { + bobmove = 0.4; // faster speeds bob faster + footstep = qtrue; + + if ( ps->weaponstate == WEAPON_READY ) + PM_ContinueTorsoAnim( PM_GetAnim( ANIM_RUNB, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse ); + PM_ContinueLegsAnim( PM_GetAnim( ANIM_RUNB, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ), qtrue ); //LEGS_BACK + } else { + bobmove = 0.3; + + if ( ps->weaponstate == WEAPON_READY ) + PM_ContinueTorsoAnim( PM_GetAnim( ANIM_WALKB, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse ); + PM_ContinueLegsAnim( PM_GetAnim( ANIM_WALKB, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ), qtrue ); //LEGS_BACK + } + + } else { + + if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { + bobmove = 0.4; // faster speeds bob faster + footstep = qtrue; + + if ( ps->weaponstate == WEAPON_READY ) + PM_ContinueTorsoAnim( PM_GetAnim( ANIM_RUN, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse ); + PM_ContinueLegsAnim( PM_GetAnim( ANIM_RUN, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ), qtrue ); //LEGS_RUN + + } else { + bobmove = 0.3; // walking bobs slow //0.3 + if ( ps->weaponstate == WEAPON_READY ) + PM_ContinueTorsoAnim( PM_GetAnim( ANIM_WALK, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse ); + + PM_ContinueLegsAnim( PM_GetAnim( ANIM_WALK, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ), qtrue ); //LEGS_WALK + } + } + + // check for footstep / splash sounds + old = ps->bobCycle; + ps->bobCycle = (int)( old + bobmove * pml.msec ) & 255; + + // if we just crossed a cycle boundary, play an apropriate footstep event + if ( ( ( old + 64 ) ^ ( ps->bobCycle + 64 ) ) & 128 && !ps->stats[PW_INVIS] ) { + if ( pm->watertype & CONTENTS_LADDER ) {// on ladder + if ( !pm->noFootsteps ) { + PM_AddEvent( EV_FOOTSTEP_METAL ); + } + } else if ( pm->waterlevel == 0 ) { + // on ground will only play sounds if running + if ( footstep && !pm->noFootsteps ) { + PM_AddEvent( PM_FootstepForSurface() ); + } + } else if ( pm->waterlevel == 1 ) { + // splashing + PM_AddEvent( EV_FOOTSPLASH ); + } else if ( pm->waterlevel == 2 ) { + // wading / swimming at surface + PM_AddEvent( EV_SWIM ); + } else if ( pm->waterlevel == 3 ) { + // no sound when completely underwater + + } + } +} + +/* +============== +PM_WaterEvents +============== +*/ +/** +* Generate sound events for entering and leaving water +*/ +static void PM_WaterEvents( void ) { // FIXME? + if ( pm->watertype & CONTENTS_LADDER || pm->ps->stats[PW_INVIS] ) //fake water for ladder + { + return; + } + // + // if just entered a water volume, play a sound + // + if (!pml.previous_waterlevel && pm->waterlevel) { + PM_AddEvent( EV_WATER_TOUCH ); + } + + // + // if just completely exited a water volume, play a sound + // + if (pml.previous_waterlevel && !pm->waterlevel) { + PM_AddEvent( EV_WATER_LEAVE ); + } + + // + // check for head just going under water + // + if (pml.previous_waterlevel != 3 && pm->waterlevel == 3) { + PM_AddEvent( EV_WATER_UNDER ); + } + + // + // check for head just coming out of water + // + if (pml.previous_waterlevel == 3 && pm->waterlevel != 3) { + PM_AddEvent( EV_WATER_CLEAR ); + } +} + + +/* +=============== +PM_BeginWeaponChange +=============== +*/ +/** +* Begins weapon change +*/ +static void PM_BeginWeaponChange( int weapon ) { + playerState_t *ps = pm->ps; + + if ( weapon <= WP_NONE || weapon >= WP_NUM_WEAPONS ) { + return; + } + + if ( !( ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) { + return; + } + + if ( ps->weaponstate == WEAPON_DROPPING ) { + return; + } + + PM_AddEvent( EV_CHANGE_WEAPON ); + ps->weaponstate = WEAPON_DROPPING; + ps->weaponTime += 200; + PM_ForceTorsoAnim( TORSO_DROPWEAP1, qfalse ); +} + + +/* +=============== +PM_FinishWeaponChange +=============== +*/ +/** +* Finishs weapon change +*/ +static void PM_FinishWeaponChange( void ) { + int weapon; + playerState_t *ps = pm->ps; + + weapon = pm->cmd.weapon; + if ( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS ) { + weapon = WP_NONE; + } + + if ( !( ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) { + weapon = WP_NONE; + } + + ps->weapon = weapon; + ps->weaponstate = WEAPON_RAISING; + ps->weaponTime += 250; + PM_ForceTorsoAnim( TORSO_RAISEWEAP1, qfalse ); +} + + +/* +============== +PM_TorsoAnimation + +============== +*/ +/** +* Once handled torso animation +*/ +static void PM_TorsoAnimation( void ) +{ + + if ( pm->ps->weaponstate == WEAPON_READY ) + { + /*if ( pm->ps->weapon == WP_PHASER || //RPG-X - TiM: Making the default pose anim better + pm->ps->weapon == WP_DISRUPTOR ) + { + PM_ContinueTorsoAnim( TORSO_WEAPONIDLE1 ); + } + else + { + PM_ContinueTorsoAnim( TORSO_WEAPONREADY2 ); + } + if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { + //PM_ContinueTorsoAnim( BOTH_RUN1 ); + } + else { + PM_ContinueTorsoAnim( BOTH_WALK1 ); + }*/ + } + + //PM_ContinueTorsoAnim( TORSO_STAND2 ); + return; +} + + +#define PHASER_AMMO_PER_SHOT 1 +#define PHASER_ALT_AMMO_PER_SHOT 2 + +//! alt ammo usage +int altAmmoUsage[WP_NUM_WEAPONS] = +{ + 0, //!ps; + + // don't allow attack until all buttons are up + if ( ps->pm_flags & PMF_RESPAWNED ) { + return; + } + + // ignore if spectator + if ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + return; + } + + //Check for phaser ammo recharge + //RPG-X: Marcin: don't! - 30/12/2008 + if ( 0 ) //( (ps->rechargeTime <= 0) && ( ps->ammo[WP_PHASER] < PHASER_AMMO_MAX) ) + { + ps->rechargeTime = PHASER_RECHARGE_TIME; + ps->ammo[WP_PHASER]++; + } + else + { + ps->rechargeTime -= pml.msec; + } + + // check for dead player + if ( ps->stats[STAT_HEALTH] <= 0 ) { + ps->weapon = WP_NONE; + return; + } + + // check for item using + if ( pm->cmd.buttons & BUTTON_USE_HOLDABLE ) + { + if ( ! ( ps->pm_flags +& PMF_USE_ITEM_HELD ) ) + { + // I have commented out this code because, we want the medkit to ALWAYS be used. + +// if ( bg_itemlist[ps->stats[STAT_HOLDABLE_ITEM]].giTag == HI_MEDKIT +// && ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] ) +// { +// // don't use medkit if at max health +// } +// else + { + int tag = bg_itemlist[ps->stats[STAT_HOLDABLE_ITEM]].giTag; + ps->pm_flags |= PMF_USE_ITEM_HELD; + PM_AddEvent( EV_USE_ITEM0 + tag ); + // if we're placing the detpack, don't remove it from our "inventory" + if ( (HI_DETPACK == tag) /* || (HI_TRANSPORTER == tag)) */ && + (IT_HOLDABLE == bg_itemlist[ps->stats[STAT_HOLDABLE_ITEM]].giType) ) + { + // are we placing it? + if (2 == ps->stats[STAT_USEABLE_PLACED]) + { + // we've placed the first stage of a 2-stage transporter + } + else if (ps->stats[STAT_USEABLE_PLACED]) + { + // we already placed it, we're activating it. + ps->stats[STAT_HOLDABLE_ITEM] = 0; + } + } + else + { + ps->stats[STAT_HOLDABLE_ITEM] = 0; + } + } + return; + } + } + else + { + ps->pm_flags &= ~PMF_USE_ITEM_HELD; + } + + + // make weapon function + if ( ps->weaponTime > 0 ) { + ps->weaponTime -= pml.msec; + } + + // check for weapon change + // can't change if weapon is firing, but can change + // again if lowering or raising + if ( ps->weaponTime <= 0 || ps->weaponstate != WEAPON_FIRING ) { + if ( ps->weapon != pm->cmd.weapon ) { + PM_BeginWeaponChange( pm->cmd.weapon ); + } + } + + if ( ps->weaponTime > 0 ) { + return; + } + + // change weapon if time + if ( ps->weaponstate == WEAPON_DROPPING ) { + PM_FinishWeaponChange(); + return; + } + + if ( ps->weaponstate == WEAPON_RAISING ) + { + ps->weaponstate = WEAPON_READY; + /*if ( ps->weapon == WP_PHASER || //RPG-X - TiM: Making the default pose anim better + ps->weapon == WP_DISRUPTOR ) + { + PM_StartTorsoAnim( TORSO_WEAPONREADY2 ); + } + else + { + PM_StartTorsoAnim( TORSO_WEAPONIDLE1 ); + }*/ + + PM_StartTorsoAnim( PM_GetAnim( ANIM_IDLE, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse ); + + return; + } + + // check for fire + if ( !(pm->cmd.buttons & (BUTTON_ATTACK | BUTTON_ALT_ATTACK) ) ) + { + ps->weaponTime = 0; + ps->weaponstate = WEAPON_READY; + + return; + } + + // take an ammo away if not infinite + if ( ps->ammo[ ps->weapon ] != -1 ) + { + if (pm->cmd.buttons & BUTTON_ALT_ATTACK) + { + // alt fire + // check for out of ammo + if ( ps->ammo[ps->weapon] < altAmmoUsage[ps->weapon]) + { + //FIXME: flash a message and sound that indicates not enough ammo +// PM_AddEvent( EV_NOAMMO_ALT ); +// ps->weaponTime += 500; +// ps->eFlags &= ~EF_ALT_FIRING; +// ps->eFlags &= ~EF_FIRING; +/// ps->weaponstate = WEAPON_READY; + + /* + RPG-X | Phenix | 27/02/2005 + if ( ps->weapon == WP_PHASER ) // phaser out of ammo is special case + { + ps->ammo[ps->weapon] = 0; + } + else + { + PM_AddEvent( EV_NOAMMO_ALT ); // Just try to switch weapons like any other. + + // check out the EV_NOAMMO_ALT event + + ps->weaponTime += 500; + return; + }*/ + } + else + { + //ps->ammo[ps->weapon] -= altAmmoUsage[ps->weapon]; + //RPG-X | Phenix | 27/02/2005 + //altfired = qtrue; + } + altfired = qtrue; + } + else + { + // check for out of ammo + if ( ! ps->ammo[ ps->weapon ] ) + { + if ( ps->weapon == WP_PHASER ) // phaser out of ammo is special case + { + ps->ammo[ps->weapon] = 0; + } + //else //TiM - No ammo no more... this is lagging us up + //{ + // PM_AddEvent( EV_NOAMMO ); + // ps->weaponTime += 500; + // return; + //} + } + else + { + // main fire (always uses 1 ammo) + //ps->ammo[ps->weapon]--; + //RPG-X | Phenix | 27/02/2005 + } + } + } + + // *don't* start the animation if out of ammo + /*if ( ps->weapon == WP_PHASER || //RPG-X - TiM: Making the default pose anim better + ps->weapon == WP_DISRUPTOR ) + { + PM_StartTorsoAnim( TORSO_ATTACK2 ); + } + else + { + PM_StartTorsoAnim( BOTH_ATTACK1 ); + }*/ + + if ( ps->weapon != WP_TOOLKIT && ps->weapon != WP_COFFEE && ps->weapon != WP_NULL_HAND ) + { + //Little hack. I like the idle poses for these when it crouches :) + if ( ( ( ps->weapon == WP_PHASER ) + || ( ps->weapon == WP_COMPRESSION_RIFLE ) + || ( ps->weapon == WP_DISRUPTOR ) + || ( ps->weapon == WP_TR116 ) ) + && ( ps->pm_flags & PMF_DUCKED /*&& !pm->xyspeed*/ ) ) + { + PM_ForceTorsoAnim( PM_GetAnim( ANIM_CROUCH, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse ); + /*BWEEE*/ + //PM_ContinueTorsoAnim( PM_GetAnim( ANIM_ATTACK, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse ); + } + else + { + /*if ( !PM_HoldingLoopableWeapon() ) { + PM_ForceTorsoAnim( PM_GetAnim( ANIM_FIRE, qtrue ), qtrue ); + } + else {*/ + //PM_ContinueLegsAnim( PM_GetAnim( ANIM_ATTACK, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qfalse ), qfalse ); + PM_ContinueTorsoAnim( PM_GetAnim( ANIM_ATTACK, ps->weapon, ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH), qtrue ), qfalse ); + //} + } + /*else + PM_StartTorsoAnim( PM_GetAnim( "fire", qtrue ) );*/ + + //Put in this scope, so holding down the trigger on these 'no-anim' weapons won't lock + //other animations. + //Bots were locked in the jumping anim >.< + + ps->weaponstate = WEAPON_FIRING; + } + else + { + ps->weaponstate = WEAPON_READY; + + } + + // fire weapon + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + if (altfired) // it's either a legally altfirable non-phaser, or it's a phaser with ammo left + { + PM_AddEvent( EV_ALT_FIRE ); // fixme, because I'm deducting ammo earlier, the last alt-fire shot turns into a main fire + } + else + { + PM_AddEvent( EV_FIRE_EMPTY_PHASER ); + } + switch( ps->weapon ) { + default: + case WP_PHASER: + addTime = 100; + //If the phaser has been fired, delay the next recharge time + ps->rechargeTime = PHASER_RECHARGE_TIME; + break; + case WP_DERMAL_REGEN: + addTime = 0; //500 + break; + case WP_GRENADE_LAUNCHER: + addTime = 600;//RPG-X: RedTechie use to be 700 + break; + case WP_DISRUPTOR: + addTime = DISRUPTOR_DELAY; + break; + case WP_COFFEE: + addTime = 0; //700 + break; + case WP_QUANTUM_BURST: + addTime = ALT_PHOTON_DELAY; + break; + case WP_NULL_HAND: + addTime = 460; //700 + break; + case WP_COMPRESSION_RIFLE: + addTime = 100; + break; + case WP_TR116: + addTime = 500; //RPG-X: RedTechie - Use to be 1200 + break; + case WP_VOYAGER_HYPO: + //RPG-X: RedTechie - Admins get faster alt fire for steam effects + if(/*ps->persistant[PERS_CLASS] == PC_ADMIN*/ pm->admin){ + addTime = 80; + }else{ + addTime = 1000; + } + break; + case WP_TOOLKIT: + addTime = 2000; + break; + case WP_MEDKIT: + addTime = 0; //1000 + break; + case WP_TRICORDER: + if(pm->admin /*ps->persistant[PERS_CLASS] == PC_ADMIN*/){ + addTime = ALT_TRICORDER_DELAY; + }else{ + addTime = 0; + } + break; + case WP_PADD: + addTime = 0; //500 + break; + case WP_HYPERSPANNER: + addTime = 0; //1000 + break; +/* case WP_TR116: + addTime = 500; //RPG-X: RedTechie - Use to be 1200 + break;*/ + } + } + else + { + if (ps->ammo[ps->weapon]) + { + PM_AddEvent( EV_FIRE_WEAPON ); + } + else + { + PM_AddEvent( EV_FIRE_EMPTY_PHASER ); + } + switch( ps->weapon ) { + default: + case WP_PHASER: + addTime = 100; + //If the phaser has been fired, delay the next recharge time + ps->rechargeTime = PHASER_RECHARGE_TIME; + break; + case WP_DERMAL_REGEN: + addTime = 1000; //1000 + break; + case WP_GRENADE_LAUNCHER: + addTime = 460;//RPG-X: RedTechie use to be 700 + break; + case WP_NULL_HAND: + addTime = 460; + break; + case WP_DISRUPTOR: + addTime = 100; + break; + case WP_COFFEE: + addTime = 0; //100 + break; + case WP_QUANTUM_BURST: + addTime = PHOTON_DELAY; + break; + case WP_COMPRESSION_RIFLE: + addTime = RIFLE_DELAY; + break; + case WP_TR116: + addTime = TR116_DELAY; //RPG-X: RedTechie - Use to be 1200 + break; + case WP_VOYAGER_HYPO: + addTime = 1000; + break; + case WP_TOOLKIT: + addTime = 2000; //1000 + break; + case WP_MEDKIT: + addTime = 0; //1000 + break; + case WP_TRICORDER: + addTime = ALT_TRICORDER_DELAY; //1000 + break; + case WP_PADD: + addTime = 0; //500 + break; + case WP_HYPERSPANNER: + addTime = 0; //1000 + break; +/* case WP_TR116: + addTime = 500; //RPG-X: RedTechie - Use to be 1200 + break;*/ + } + } + + if ( ps->powerups[PW_HASTE] ) { + addTime /= 1.3; + } + + ps->weaponTime += addTime; +} + +/* +================ +PM_Animate +================ +*/ +static void PM_Animate( void ) { + playerState_t *ps = pm->ps; + + if ( pm->cmd.buttons & BUTTON_GESTURE ) { + if (ps->pm_type < PM_DEAD ) { + //if ( ps->torsoTimer == 0 ) { + //PM_StartTorsoAnim( BOTH_CONSOLE1 ); + //PM_StartLegsAnim( BOTH_CONSOLE1 ); + //ps->introTime = ps->torsoTimer = 4000; //TIMER_GESTURE //legsTimer + //PM_AddEvent( EV_TAUNT ); + + if ( !( ps->eFlags & EF_TALKING ) ) { + ps->eFlags |= EF_TALKING; + } + } + } else { + if ( ( ps->eFlags & EF_TALKING ) ) { + ps->eFlags &= ~EF_TALKING; + } + } +} + + +/* +================ +PM_DropTimers +================ +*/ +static void PM_DropTimers( void ) { + int newFlags; + playerState_t *ps = pm->ps; + + // drop misc timing counter + if ( ps->pm_time ) { + if ( pml.msec >= ps->pm_time ) + { + ps->pm_flags &= ~PMF_ALL_TIMES; + ps->pm_time = 0; + } + else + { + ps->pm_time -= pml.msec; + } + } + + //Count down the legs anim timer + if ( ps->stats[LEGSTIMER] > 0 ) { //legsTimer + ps->stats[LEGSTIMER] -= pml.msec; + if ( ps->stats[LEGSTIMER] < 0 ) + { + ps->stats[LEGSTIMER] = 0; + } + } + + //if legs anim timer hit 0 + if ( ps->stats[LEGSTIMER] == 0 ) { + if ( (ps->stats[EMOTES] & EMOTE_LOWER ) && + !(ps->stats[EMOTES] & EMOTE_LOOP_LOWER ) ) + { + ps->stats[EMOTES] &= ~EMOTE_MASK_LOWER; + + if ( ps->legsAnim > 0 ) + { + PM_ForceLegsAnim( bg_emoteList[ps->legsAnim].enumName ); + + //TiM - Remove any data about torsos here. it's not necessary and it invalidates the check below + newFlags = ( bg_emoteList[ps->legsAnim].animFlags | bg_emoteList[ps->legsAnim].bodyFlags ); + newFlags &= ~EMOTE_MASK_UPPER; + ps->stats[EMOTES] |= newFlags; + //Com_Printf( S_COLOR_RED "Set anim to %i\n", ps->legsAnim ); + + ps->legsTimer = ps->legsAnim; + //ps->torsoTimer = emoteList[ps->torsoAnim].hitBoxHeight; + } + else + { + ps->legsTimer = 0; + ps->legsAnim = 0; + } + //Com_Printf(S_COLOR_RED "Acknowledge Lower change!!\n"); + } + } + + if ( ps->stats[TORSOTIMER] > 0 ) { //torsoTimer + ps->stats[TORSOTIMER] -= pml.msec; + if ( ps->stats[TORSOTIMER] < 0 ) + { + ps->stats[TORSOTIMER] = 0; + + /*if (ps->eFlags & EF_EMOTING ) { + ps->eFlags &= ~EF_EMOTING; + }*/ + } + } + + if ( ps->stats[TORSOTIMER] == 0 ) { + if ( (ps->stats[EMOTES] & EMOTE_UPPER) && + !(ps->stats[EMOTES] & EMOTE_LOOP_UPPER ) ) + { + ps->stats[EMOTES] &= ~EMOTE_MASK_UPPER; + + if ( ps->torsoAnim > 0 ) + { + PM_ForceTorsoAnim( bg_emoteList[ps->torsoAnim].enumName, qtrue ); + + //TiM - Remove any data about legs here. it's not necessary and it invalidates any subsequent checks + newFlags = ( bg_emoteList[ps->torsoAnim].animFlags | bg_emoteList[ps->torsoAnim].bodyFlags ); + newFlags &= ~EMOTE_MASK_LOWER; + ps->stats[EMOTES] |= newFlags; + //ps->stats[EMOTES] |= ( emoteList[ps->torsoAnim].animFlags | emoteList[ps->torsoAnim].bodyFlags ); + + } + else + { + ps->torsoTimer = 0; + ps->torsoAnim = 0; + } + } + } + + // drop animation counter +/* if ( ps->introTime > 0 ) { //legsTimer + ps->introTime -= pml.msec; + if ( ps->introTime < 0 ) { + ps->introTime = 0; + + if (ps->eFlags & EF_EMOTING ) { + ps->eFlags &= ~EF_EMOTING; + } + } + } + + if ( ps->torsoTimer > 0 ) { + ps->torsoTimer -= pml.msec; + if ( ps->torsoTimer < 0 ) { + ps->torsoTimer = 0; + } + }*/ +} + +/* +================ +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; + + if ( ps->pm_type == PM_INTERMISSION ) { + return; // no view changes at all + } + + if ( ps->pm_type != PM_SPECTATOR && ps->stats[STAT_HEALTH] <= 1 ) { //RPG-X: RedTechie - This use to be 0 but in rpg-x with or without medics revive 1 health means you die! + return; // no view changes at all + } + + + //TiM - Bookmark + //With a flag here, a change to the client side player rotation code there, + //we could actually make it EVA suit users could rotate fully in all directions + //when in space. :) + + //Com_Printf( "Before: %f %f %f\n", ps->viewangles[0], ps->viewangles[1], ps->viewangles[2]); + + // circularly clamp the angles with deltas + for (i=0 ; i<3 ; i++) { + temp = cmd->angles[i] + ps->delta_angles[i]; + if ( i == PITCH && !pm->ps->powerups[PW_FLIGHT] && !(( pm->ps->powerups[PW_EVOSUIT] ) && ( pm->ps->gravity == 0 )) ) { + // don't let the player look up or down more than 90 degrees + if ( temp > 16000 ) { + ps->delta_angles[i] = 16000 - cmd->angles[i]; + temp = 16000; + } else if ( temp < -16000 ) { + ps->delta_angles[i] = -16000 - cmd->angles[i]; + temp = -16000; + } + } + + ps->viewangles[i] = SHORT2ANGLE(temp); + } +} + + +/* +================ +PmoveSingle + +================ +*/ +void PmoveSingle (pmove_t *pmove) { + playerState_t *ps = pmove->ps; + + 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(ps->legsTimer > 0 ) { + // Com_Printf("%i, %i\n", ps->legsTimer, ps->torsoTimer); + //} + + if ( ps->stats[STAT_HEALTH] <= 0 ) { + pm->tracemask &= ~CONTENTS_BODY; // corpses can fly through bodies + } + + // make sure walking button is clear if they are running, to avoid + // proxy no-footsteps cheats + if ( abs( pm->cmd.forwardmove ) > 64 || abs( pm->cmd.rightmove ) > 64 ) { + pm->cmd.buttons &= ~BUTTON_WALKING; + } + + // set the talk balloon flag + if ( pm->cmd.buttons & BUTTON_TALK ) { + ps->eFlags |= EF_TALK; + } else { + ps->eFlags &= ~EF_TALK; + } + + // set the firing flag for continuous beam weapons + if ( !(ps->pm_flags & PMF_RESPAWNED) && + ps->pm_type != PM_INTERMISSION && + ( (pm->cmd.buttons & BUTTON_ATTACK) || (pm->cmd.buttons & BUTTON_ALT_ATTACK) ) && + (ps->ammo[ ps->weapon ] || ps->weapon == WP_PHASER)) + { + if (((ps->weapon == WP_PHASER) && (!ps->ammo[ ps->weapon ])) || (!(pm->cmd.buttons & BUTTON_ALT_ATTACK))) + { + ps->eFlags &= ~EF_ALT_FIRING; + } + else // if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) <-- implied + { + ps->eFlags |= EF_ALT_FIRING; + } + + /*if ( ps->weapon == WP_DISRUPTOR ) + {//tech can't use alt attack + pm->cmd.buttons &=~BUTTON_ALT_ATTACK; + pm->cmd.buttons |= BUTTON_ATTACK; + }*/ + //TiM - good riddance to bad coding + + // This flag should always get set, even when alt-firing + ps->eFlags |= EF_FIRING; + } + else + { + ps->eFlags &= ~EF_FIRING; + ps->eFlags &= ~EF_ALT_FIRING; + } + + // clear the respawned flag if attack and use are cleared + if ( ps->stats[STAT_HEALTH] > 0 && + !( pm->cmd.buttons & (BUTTON_ATTACK | BUTTON_USE_HOLDABLE) ) ) { + ps->pm_flags &= ~PMF_RESPAWNED; + } + + // if talk button is down, dissallow all other input + // this is to prevent any possible intercept proxy from + // adding fake talk balloons + if ( pmove->cmd.buttons & BUTTON_TALK ) + { + pmove->cmd.buttons = 0; + pmove->cmd.forwardmove = 0; + pmove->cmd.rightmove = 0; + if (pmove->cmd.upmove) + { + if (pmove->cmd.upmove > 0) + { + pmove->cmd.upmove = 1; + } + else + { + pmove->cmd.upmove = -1;//allow a tiny bit to keep the duck anim + } + } + } + + //ph34r teh cheezy hack + //Disable the ability to jump when getting up animating. + //This is for the purporse of the medics revive animation + if ( ( ps->stats[LEGSANIM] & ~ANIM_TOGGLEBIT ) == BOTH_GET_UP1 ) { + pm->cmd.upmove = 0; + } + + //If the legs are playing a non infinite loop, disable the movement keys. + //Else the player slides on their feet :S + if ( ps->stats[EMOTES] & EMOTE_LOWER &&( ps->stats[EMOTES] & EMOTE_CLAMP_BODY ) || ( ps->stats[EMOTES] & EMOTE_CLAMP_ALL ) ) { //EMOTE_LOWER + pmove->cmd.forwardmove = 0; + pmove->cmd.rightmove = 0; + //pmove->cmd.upmove = Q_fabs( pmove->cmd.upmove ); + } + + if ( ps->stats[EMOTES] & ( EMOTE_LOWER | EMOTE_UPPER ) ) { + if (pmove->cmd.buttons & MASK_KILL_EMOTES || pmove->cmd.upmove != 0 ) { + ps->stats[EMOTES] &= ~EMOTE_MASK_BOTH; + + ps->legsTimer = 0; + ps->stats[LEGSTIMER] = 0; + } + } + + //TiM: Injured system + //Disable the ability to toggle walking, + //and slow player down. :) + if ( ps->stats[STAT_HEALTH] <= INJURED_MODE_HEALTH ) { + pm->cmd.buttons &= ~BUTTON_WALKING; + } + + //TiM : Override for looping animation emotes + //If player touches move keys, cancel emotes + /*if ( pmove->cmd.upmove || pmove->cmd.rightmove || pmove->cmd.forwardmove || ( pmove->cmd.buttons & MASK_KILL_EMOTES ) ) { + //if ( ( ps->pm_flags & ANIM_LOWER_LOOPING ) || ( ps->pm_flags & ANIM_UPPER_LOOPING ) ) { + if ( ps->stats[EMOTES] & EMOTE_LOWER || ps->stats[EMOTES] & EMOTE_UPPER ) { + ps->stats[EMOTES] &= ~EMOTE_MASK_BOTH; + + ps->legsTimer = -1; + //ps->torsoTimer = 0; + } + }*/ + + // clear all pmove local vars + memset (&pml, 0, sizeof(pml)); + + // determine the time + pml.msec = pmove->cmd.serverTime - ps->commandTime; + if ( pml.msec < 1 ) { + pml.msec = 1; + } else if ( pml.msec > 200 ) { + pml.msec = 200; + } + ps->commandTime = pmove->cmd.serverTime; + + // save old org in case we get stuck + VectorCopy (ps->origin, pml.previous_origin); + + // save old velocity for crashlanding + VectorCopy (ps->velocity, pml.previous_velocity); + + pml.frametime = pml.msec * 0.001; + + if (ps->pm_type == PM_FREEZE /*|| ps->introTime > pm->cmd.serverTime*/ ) + { + PM_FreezeMove(); + return; // no movement at all + } + + // update the viewangles + PM_UpdateViewAngles( ps, &pm->cmd ); + + AngleVectors (ps->viewangles, pml.forward, pml.right, pml.up); + + if ( pm->cmd.upmove < 10 ) { + // not holding jump + ps->pm_flags &= ~PMF_JUMP_HELD; + } + + // decide if backpedaling animations should be used + if ( pm->cmd.forwardmove < 0 ) { + ps->pm_flags |= PMF_BACKWARDS_RUN; + } else if ( pm->cmd.forwardmove > 0 || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) { + ps->pm_flags &= ~PMF_BACKWARDS_RUN; + } + + if ( ps->pm_type >= PM_DEAD ) { + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + } + + + if ( ps->pm_type == PM_SPECTATOR ) { + PM_CheckDuck (); + PM_FlyMove (); + PM_DropTimers (); + return; + } + + + if ( ps->pm_type == PM_SPECTATOR ) + { + //spectator or an eliminated player + + //RPG-X J2J - NoClip Spectation: + //Change to allow noclip in spectator if enabled on server + //And if the client wants to noclip + /*if(rpg_noclipspectating.integer == 1 && ClientNCSpec == qtrue) + { + PM_NoclipMove(); + PM_DropTimers (); + return; + }*/ + //Else allow flight. + //else + { + PM_CheckDuck (); + PM_FlyMove(); + PM_DropTimers (); + return; + } + //////////////////////////////// + } + + if ( ps->pm_type == PM_NOCLIP ) { + PM_NoclipMove (); + PM_DropTimers (); + return; + } + + if ( ps->pm_type == PM_INTERMISSION ) { + return; // no movement at all + } + + // set watertype, and waterlevel + PM_SetWaterLevel(); + if ( !(pm->watertype & CONTENTS_LADDER) ) + {//Don't want to remember this for ladders, is only for waterlevel change events (sounds) + pml.previous_waterlevel = pmove->waterlevel; + } + + // set mins, maxs, and viewheight + PM_CheckDuck (); + + // set groundentity + PM_GroundTrace(); + + if ( ps->pm_type == PM_DEAD ) { + PM_DeadMove (); + } + + if ( ps->powerups[PW_FLIGHT] ) { + // flight powerup doesn't allow jump and has different friction + PM_FlyMove(); + } else if ((( ps->powerups[PW_EVOSUIT] ) && ( ps->gravity == 0 ))) { + //RPG-X | Phenix | 8/8/2004 + //The player is in 0 G and is wearing an evo suit... + PM_FlyMove(); + } else if (ps->pm_flags & PMF_TIME_WATERJUMP) { + PM_WaterJumpMove(); + } else if ( pm->waterlevel > 1 ) { + // swimming + PM_WaterMove(); + } else if ( pml.walking ) { + // walking on ground + PM_WalkMove(); + } else { + // airborne + PM_AirMove(); + } + + PM_Animate(); + + // set groundentity, watertype, and waterlevel + PM_GroundTrace(); + PM_SetWaterLevel(); + + // weapons + PM_Weapon(); + + PM_Use(); + + // torso animation + PM_TorsoAnimation(); + + // footstep events / legs animations + PM_Footsteps(); + + // entering / leaving water splashes + PM_WaterEvents(); + + //PM_DoEmote(); + + PM_DropTimers(); + + // snap some parts of playerstate to save network bandwidth + SnapVector( ps->velocity ); +} + + +/* +================ +Pmove +================ +*/ +/** +* Can be called by either the server or the client +*/ +void Pmove (pmove_t *pmove) { + int finalTime; + + finalTime = pmove->cmd.serverTime; + + if ( finalTime < pmove->ps->commandTime ) { + return; // should not happen + } + + if ( finalTime > pmove->ps->commandTime + 1000 ) { + pmove->ps->commandTime = finalTime - 1000; + } + + // chop the move up if it is too long, to prevent framerate + // dependent behavior + while ( pmove->ps->commandTime != finalTime ) { + int msec; + + msec = finalTime - pmove->ps->commandTime; + + if ( msec > 66 ) { + msec = 66; + } + pmove->cmd.serverTime = pmove->ps->commandTime + msec; + PmoveSingle( pmove ); + } + +} diff --git a/game/bg_public.h b/game/bg_public.h new file mode 100644 index 0000000..1c03175 --- /dev/null +++ b/game/bg_public.h @@ -0,0 +1,1169 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_public.h -- definitions shared by both the server game and client game modules + +// are we compiling for rpgxEF? +#define XTRA 1 +// meh somehow preprocessor G_LUA won't work for me +#define G_LUA 1 +#define CG_LUA 1 + +// because games can change separately from the main system version, we need a +// second version that must match between game and cgame +#define RPGX_VERSION "2.2 Beta 8.4.6" +#define RPGX_COMPILEDATE "20/05/11" +#define RPGX_COMPILEDBY "GSIO01" +//const char GAME_VERSION[] = strcat("RPG-X v",RPGX_VERSION); +//ehem why not: +#define GAME_VERSION "RPG-X v" RPGX_VERSION + +#define INJURED_MODE_HEALTH 20 //! MAX_CONFIGSTRINGS +#error overflow: (CS_MAX) > MAX_CONFIGSTRINGS +#endif + +typedef enum { + GT_FFA, //!< free for all + GT_TOURNAMENT, //!< one on one tournament + GT_SINGLE_PLAYER, //!< single player tournament + + //-- team games go after this -- + + GT_TEAM, //!< team deathmatch + GT_CTF, //!< capture the flag + + GT_MAX_GAME_TYPE +} gametype_t; + +typedef enum { GENDER_MALE, GENDER_FEMALE, GENDER_NEUTER } gender_t; + +//TiM - Global variables for the player weight/height system +#define BASE_HEIGHT 185.0 +#define BASE_WEIGHT 90.0 + +#define FEMALE_OFFSET 0.73 + +#define MAX_HEIGHT 1.15 +#define MIN_HEIGHT 0.9 + +#define MAX_WEIGHT 1.1 +#define MIN_WEIGHT 0.9 + +#define HEIGHT_UNIT "cm" +#define WEIGHT_UNIT "kg" + +/* +=================================================================================== + +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. +=================================================================================== +*/ + +//RPG-X - Player Model System. Anim on or off states +/*typedef enum { + ANIM_ONLADDER, + ANIM_OFFLADDER, + ANIM_DIDFLY +} animtype_t;*/ + +/** \enum pmtype_t +* +*/ +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; + +/** \enum weaponstate_t +* +*/ +typedef enum { + WEAPON_READY, + WEAPON_RAISING, + WEAPON_DROPPING, + WEAPON_FIRING +} weaponstate_t; + +// pmove->pm_flags + +#define PMF_DUCKED 1 //1 //2 +#define PMF_JUMP_HELD 2 //2 //4 +#define PMF_BACKWARDS_JUMP 8 //!< go into backwards land +#define PMF_BACKWARDS_RUN 16 //!< coast down to backwards run +#define PMF_TIME_LAND 32 //!< pm_time is time before rejump +#define PMF_TIME_KNOCKBACK 64 //!< pm_time is an air-accelerate only time +#define PMF_TIME_WATERJUMP 256 //!< pm_time is waterjump +#define PMF_RESPAWNED 512 //!< clear after attack and jump buttons come up +#define PMF_USE_ITEM_HELD 1024 +//#define PMF_GRAPPLE_PULL 2048 //!< pull towards grapple location +#define PMF_FOLLOW 4096 //!< spectate following another player +#define PMF_SCOREBOARD 8192 //!< spectate as a scoreboard + +#define ANIM_ONLADDER 16384 +#define ANIM_DIDFLY 32768 +#define ANIM_ALERT 128 //TiM: How was this missed!? +#define ANIM_ALERT2 2048 //TiM: This makes it full I think +//#define ANIM_LOWER_LOOPING 2048 + +#define PMF_ALL_TIMES (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK) + +#define MAXTOUCH 32 + +/** +* \struct pmove_t +*/ +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 pModDisintegration; //!< true if the Disintegration playerMod is on + + // results (out) + int numtouch; + int touchents[MAXTOUCH]; + + int useEvent; + + vec3_t mins, maxs; //!< bounding box size + + int watertype; + int waterlevel; + + float xyspeed; + float xyzspeed; //TiM : in case body is falling as well + + qboolean admin; + qboolean medic; + qboolean borg; + + //RPG-X - J2J - Adding a last fire timer for weapon animations + ///int LastFireTime; + + // callbacks to test the world + // these will be different functions during game and cgame + void (*trace)( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask ); + int (*pointcontents)( const vec3_t point, int passEntityNum ); + +} pmove_t; + +// if a full pmove isn't done on the client, you can just update the angles +void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ); +void Pmove (pmove_t *pmove); + +//RPG-X: J2J - Emote Vars (shared for server and client) +//int CurrentEmote[MAX_CLIENTS]; +//QVM HACK! + +//=================================================================================== + + +// player_state->stats[] indexes +// +// maximum of MAX_STATS...currently 16 +//TiM: Ooohhh! Usable space! +/** \enum statIndex_t +* Each of these array cells can store data up to 2^16 bits of data! +* This is good in the fact that we are capable of using this place to store data +* that is larger for its previous cells, for example, animation data. +*/ +typedef enum { + STAT_HEALTH, + STAT_HOLDABLE_ITEM, + 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_MAX_HEALTH, //!< health / armor limit, changable by handicap + STAT_USEABLE_PLACED, //!< have we placed the detpack yet? + + //TiM : Placeholder for emotes data and anim holding + TORSOTIMER, //!